# \[SOLUTION\] HighLine (#29)

**URL:** <https://rubytalk.org/t/solution-highline-29/17855>\
**Category:** ruby-talk\
**Created:** [24 April 2005 15:51 UTC](https://rubytalk.org/t/solution-highline-29/17855 "2005-04-24T15:51:09Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![Ryan\_Leavengood2](https://avatars.discourse-cdn.com/v4/letter/r/8edcca/32.png) [@Ryan\_Leavengood2](https://rubytalk.org/u/Ryan_Leavengood2)\
**Post date:** [24 April 2005 15:51 UTC](https://rubytalk.org/t/solution-highline-29/17855/1 "2005-04-24T15:51:09Z")

</div>

Here is my solution for this quiz. Nothing too fancy really, but it does the job. I suspect if I used this more for various applications I'd find more clever things to do. But I did replace some command-line input/output in another program of mine with this library, and it definitely cleaned things up. It certainly makes sense to put this kind of stuff in a library instead of always rolling your own.

I never did look at the EasyPrompt code, but I suspect this code might look very similar because of my use of a MockIO object (I did notice that much from the EasyPrompt summary.) But I suppose it is good that the unit tests and MockIO object are more lines of code than the library 🙂

I would appreciate feedback on the API design as well as the unit test design. I feel the unit tests are too coupled to the implementation (particularly on my checking of the output.)

Ryan Leavengood

CODE (beware of wrapping):

> **···**
>
> #--------------------------------------  
> # HighLine command-line input library  
> #  
> # Copyright (C) 2005 Ryan Leavengood  
> #  
> # Released under the Ruby license  
> #--------------------------------------
> 
> class String  
> &nbsp;&nbsp;&nbsp;def pad\_if\_needed  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self[-1].chr != ' ' ? self + ' ' : self  
> &nbsp;&nbsp;&nbsp;end  
> end
> 
> class HighLine  
> &nbsp;&nbsp;&nbsp;attr\_accessor :io\_out, :io\_in  
> &nbsp;&nbsp;&nbsp;def initialize(io\_out=$stdout, io\_in=$stdin)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@io\_out, @io\_in = io\_out, io\_in  
> &nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;def ask(question, default=nil)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;q = question.pad\_if\_needed  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;q += "[#{default}] " if default  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;answer = validation\_loop(q) do |input|  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;input.size \> 0 or default  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;answer.size \> 0 ? answer : default  
> &nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;def ask\_if?(question)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;answer = validation\_loop(question.pad\_if\_needed+'(y,n) ') do |input|  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;%w(y n yes no).include?(input.downcase)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;answer.downcase[0,1] == 'y'  
> &nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;def ask\_int(question, range=nil)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;validation\_loop(question) do |input|  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;input =~ /\A\s\*-?\d+\s\*\Z/ and (not range or range.member?(input.to\_i))  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end.to\_i  
> &nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;def ask\_float(question, range=nil)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;validation\_loop(question) do |input|  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;input =~ /\A\s\*-?\d+(.\d\*)?\s\*\Z/ and (not range or range.member?(input.to\_f))  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end.to\_f  
> &nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;def header(title)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dashes = '-'\*(title.length+4)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;io\_out.puts(dashes)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;io\_out.puts("| #{title} |")  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;io\_out.puts(dashes)  
> &nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;def list(items, prompt=nil)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;items.each\_with\_index do |item, i|  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@io\_out.puts "#{i+1}. #{item}"  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;valid\_range = '1'..items.length.to\_s  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;prompt = "Please make a selection: " unless prompt  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;answer = validation\_loop(prompt) do |input|  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;valid\_range.member?(input)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# Though the list is shown using a 1-indexed list, return 0-indexed  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return answer.to\_i-1  
> &nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;def validation\_loop(prompt)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;loop do  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@io\_out.print prompt.pad\_if\_needed  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;answer = @io\_in.gets  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if answer  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;answer.chomp!  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if yield answer  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return answer  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;end  
> end
> 
> # Unit Tests  
> if $0 == \_\_FILE\_\_  
> &nbsp;&nbsp;&nbsp;class MockIO  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;attr\_accessor :output, :input
> 
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;def initialize  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;reset  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;def reset  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@index = 0  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@input=nil  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@output=''  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;def print(\*a)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@output \<\< a.join('')  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;def puts(\*a)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if a.size \> 1  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@output \<\< a.join("\n")  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@output \<\< a[0] \<\< "\n"  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;def gets  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if @input.kind\_of?(Array)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@index += 1  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@input[@index-1]  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@input  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;require 'test/unit'
> 
> &nbsp;&nbsp;&nbsp;class TC\_HighLine \< Test::Unit::TestCase  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;def initialize(name)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;super(name)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@mock\_io = MockIO.new  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@highline = HighLine.new(@mock\_io, @mock\_io)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;def setup  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@mock\_io.reset  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;def test\_ask  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;question = 'Am I the coolest?'  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@mock\_io.input = [nil, '', "\n", "yes\n"]  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal(@mock\_io.input[-1].chomp, @highline.ask(question))  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal((question+' ')\*4, @mock\_io.output)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;def test\_ask\_default  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;question = 'Where are you from? '  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;default = 'Florida'  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@mock\_io.input = [nil, "\n"]  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal(default, @highline.ask(question, default))  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal((question+"[#{default}] ")\*2, @mock\_io.output)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;def test\_ask\_if  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;question = 'Is Ruby the best programming language? '  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@mock\_io.input = [nil, "0\n", "blah\n", "YES\n"]  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal(true, @highline.ask\_if?(question))  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal((question+'(y,n) ')\*4, @mock\_io.output)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;def test\_ask\_int  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;question = 'Give me a number:'  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@mock\_io.input = [nil, '', "\n", ' ', "blah\n", " -4 \n"]  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal(-4, @highline.ask\_int(question))  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal((question+' ')\*6, @mock\_io.output)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@mock\_io.reset  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@mock\_io.input = [nil, '', "\n", ' ', "blah\n", "3604\n"]  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal(3604, @highline.ask\_int(question))  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal((question+' ')\*6, @mock\_io.output)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;def test\_ask\_int\_range  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;question = 'How old are you?'  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@mock\_io.input = [nil, '', "\n", ' ', "blah\n", "106\n", "28\n"]  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal(28, @highline.ask\_int(question, 0..105))  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal((question+' ')\*7, @mock\_io.output)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;def test\_ask\_float  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;question = 'Give me a floating point number:'  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@mock\_io.input = [nil, '', "\n", ' ', "blah\n", " -4.3 \n"]  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal(-4.3, @highline.ask\_float(question))  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal((question+' ')\*6, @mock\_io.output)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@mock\_io.reset  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@mock\_io.input = [nil, '', "\n", ' ', "blah\n", "560\n"]  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal(560.0, @highline.ask\_float(question))  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal((question+' ')\*6, @mock\_io.output)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;def test\_ask\_float\_range  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;question = 'Give me a floating point number between 5.0 and 13.5:'  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@mock\_io.input = [nil, '', "\n", ' ', "blah\n", " -4.3 \n", "4.9\n", "13.6\n", "7.55\n"]  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal(7.55, @highline.ask\_float(question, 5.0..13.5))  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal((question+' ')\*9, @mock\_io.output)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;def test\_header  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;title = 'HighLine Manual'  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@highline.header(title)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;output = "-------------------\n| HighLine Manual
> 
> > \n-------------------\n"
> 
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal(output, @mock\_io.output)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;def test\_list  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;items = ['Ruby','Python','Perl']  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;prompt = 'Please choose your favorite programming language: '  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@mock\_io.input = [nil, "0\n", "blah\n", "4\n", "1\n"]  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal(0, @highline.list(items, prompt))  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal("1. Ruby\n2. Python\n3. Perl\n#{prompt \* 5}", @mock\_io.output)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;end  
> end

---

<div class="post-metadata">

**Author:** ![James\_Edward\_Gray\_II](https://avatars.discourse-cdn.com/v4/letter/j/ea5d25/32.png) [@James\_Edward\_Gray\_II](https://rubytalk.org/u/James_Edward_Gray_II)\
**Post date:** [26 April 2005 23:55 UTC](https://rubytalk.org/t/solution-highline-29/17855/2 "2005-04-26T23:55:16Z")

</div>

Here's my solution.

I ran out of time, mainly for documentation, but I think I've got something worth showing here. I'm using unit tests, so you can read those (primarily "tc\_highline.rb") to see how my module works.

It covers the basics in the quiz and is pretty open for new additions. The killer feature is the type you specify in to ask(). It's really powerful in that it can take a Proc that does the conversion to whatever you like. agree(), my version of ask\_if() from the quiz, is implemented in these terms:

&nbsp;&nbsp;def agree( yes\_or\_no\_question )  
&nbsp;&nbsp;&nbsp;&nbsp;ask( yes\_or\_no\_question,  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;lambda { |a| a =~ /\AY(?:es)?\Z/i ? true : false } )  
&nbsp;&nbsp;end

Here are some other examples from my test cases:

&nbsp;&nbsp;def test\_membership  
&nbsp;&nbsp;&nbsp;&nbsp;@input \<\< "112\n-541\n28\n"  
&nbsp;&nbsp;&nbsp;&nbsp;@input.rewind

&nbsp;&nbsp;&nbsp;&nbsp;answer = @terminal.ask("Tell me your age.", Integer) do |q|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;q.member = 0..105  
&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal(28, answer)  
&nbsp;&nbsp;end  
&nbsp;&nbsp;  
&nbsp;&nbsp;def test\_reask  
&nbsp;&nbsp;&nbsp;&nbsp;number = 61676  
&nbsp;&nbsp;&nbsp;&nbsp;@input \<\< "Junk!\n" \<\< number \<\< "\n"  
&nbsp;&nbsp;&nbsp;&nbsp;@input.rewind

&nbsp;&nbsp;&nbsp;&nbsp;answer = @terminal.ask("Favorite number? ", Integer)  
&nbsp;&nbsp;&nbsp;&nbsp;assert\_kind\_of(Integer, number)  
&nbsp;&nbsp;&nbsp;&nbsp;assert\_instance\_of(Fixnum, number)  
&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal(number, answer)  
&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal( "Favorite number? " +  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"You must enter a valid Integer.\n" +  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"? ", @output.string )

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# ...  
&nbsp;&nbsp;end

&nbsp;&nbsp;def test\_type\_conversion  
&nbsp;&nbsp;&nbsp;&nbsp;# ...

&nbsp;&nbsp;&nbsp;&nbsp;@input.truncate(@input.rewind)  
&nbsp;&nbsp;&nbsp;&nbsp;number = 10.5002  
&nbsp;&nbsp;&nbsp;&nbsp;@input \<\< number \<\< "\n"  
&nbsp;&nbsp;&nbsp;&nbsp;@input.rewind

&nbsp;&nbsp;&nbsp;&nbsp;answer = @terminal.ask( "Favorite number? ",  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;lambda { |n| n.to\_f.abs.round } )  
&nbsp;&nbsp;&nbsp;&nbsp;assert\_kind\_of(Integer, answer)  
&nbsp;&nbsp;&nbsp;&nbsp;assert\_instance\_of(Fixnum, answer)  
&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal(11, answer)

&nbsp;&nbsp;&nbsp;&nbsp;# ...

&nbsp;&nbsp;&nbsp;&nbsp;@input.truncate(@input.rewind)  
&nbsp;&nbsp;&nbsp;&nbsp;@input \<\< "6/16/76\n"  
&nbsp;&nbsp;&nbsp;&nbsp;@input.rewind

&nbsp;&nbsp;&nbsp;&nbsp;answer = @terminal.ask("Enter your birthday.", Date)  
&nbsp;&nbsp;&nbsp;&nbsp;assert\_instance\_of(Date, answer)  
&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal(16, answer.day)  
&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal(6, answer.month)  
&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal(76, answer.year)

&nbsp;&nbsp;&nbsp;&nbsp;# ...

&nbsp;&nbsp;&nbsp;&nbsp;@input.truncate(@input.rewind)  
&nbsp;&nbsp;&nbsp;&nbsp;@input \<\< "gen\n"  
&nbsp;&nbsp;&nbsp;&nbsp;@input.rewind

&nbsp;&nbsp;&nbsp;&nbsp;answer = @terminal.ask("Select a mode: ", [:generate, :run])  
&nbsp;&nbsp;&nbsp;&nbsp;assert\_instance\_of(Symbol, answer)  
&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal(:generate, answer)  
&nbsp;&nbsp;end  
&nbsp;&nbsp;  
&nbsp;&nbsp;def test\_validation  
&nbsp;&nbsp;&nbsp;&nbsp;@input \<\< "system 'rm -rf /'\n105\n0b101\_001\n"  
&nbsp;&nbsp;&nbsp;&nbsp;@input.rewind

&nbsp;&nbsp;&nbsp;&nbsp;answer = @terminal.ask("Enter a binary number: ") do |q|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;q.validate = /\A(?:0b)?[01\_]+\Z/  
&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;assert\_equal("0b101\_001", answer)  
&nbsp;&nbsp;end

I had a lot of fun working on this library and may just keep working on it to see if I can't turn it into something genuinely useful.

You can load my library two different ways:

# This first way loads the class system, useful if you want to manage many HighLine  
# objects, say for socket work.  
require "highline"  
# Or you can take the easy out an import methods to the top level.  
require "highline/import"

You can find my code here:

[http://rubyquiz.com/highline.zip](http://rubyquiz.com/highline.zip)

Enjoy.

James Edward Gray II

---

<div class="post-metadata">

**Author:** ![Dave\_Burt2](https://yyz1.discourse-cdn.com/flex029/user_avatar/rubytalk.org/dave_burt2/32/2036_2.png) [@Dave\_Burt2](https://rubytalk.org/u/Dave_Burt2)\
**Post date:** [28 April 2005 08:39 UTC](https://rubytalk.org/t/solution-highline-29/17855/3 "2005-04-28T08:39:30Z")

</div>

Here's my solution:

[http://www.dave.burt.id.au/ruby/highline.rb](http://www.dave.burt.id.au/ruby/highline.rb)

It's inspired a fair bit by OptParse, and I tried to make it very flexible  
and smart in how it accepts options. The code features a little  
meta-programming (so you can do "retries 1" or "validation 1..10" in a block  
passed to the Prompt.new), optional named arguments, and inference of  
arguments' meaning by class somewhat like OptParse#on does.

It's not as easily mockable as EasyPrompt - you would have to do something  
tricky like this:  
class Prompt  
&nbsp;&nbsp;def print(\*args)  
&nbsp;&nbsp;&nbsp;&nbsp;my\_alternate\_output\_stream.print \*args  
&nbsp;&nbsp;end  
&nbsp;&nbsp;def gets  
&nbsp;&nbsp;&nbsp;&nbsp;my\_alternate\_input\_stream.gets  
&nbsp;&nbsp;end  
end

I think the code you write to use it is cleaner and more straighforward than  
the examples from the quiz question and the EasyPrompt doco. Here are those  
examples and more:

# Example usage from the Quiz  
age = ask("What is your age?", Integer, 0..105 )  
num = ask('Enter a binary number.') {|s| not s =~ /[^01]/ }.to\_i(2)  
if ask("Would you like to continue?", TrueClass) # ...

# Example usage from EasyPrompt documentation

irb(main):003:0\> fname = prompt.ask( "What's your first name?" )  
What's your first name? John  
=\> "John"  
irb(main):004:0\> lname = ask("What's your last name?", "Doe")  
What's your last name? [Doe]  
=\> "Doe"  
irb(main):005:0\> ok = ask("Is your name #{ fname } #{ lname }?", TrueClass)  
Is your name John Doe? [Yn]  
=\> true

# Extra examples  
s = ask  
i = ask("How many strikes will be allowed?", 3, 0..(1.0/0.0))  
s = ask("Give me a word with "foo" in it:", /foo/)  
i = ask("Give me a number divisible by 3:", Integer) {|i| i % 3 == 0 }  
a = ask("Give me three or more numbers:", Array) do |x|  
&nbsp;&nbsp;&nbsp;&nbsp;x.each {|n| Float(n) }  
&nbsp;&nbsp;&nbsp;&nbsp;x.size \>= 3 or puts "I need more things than that!"  
end.map {|n| n.to\_f }

# And you can also get a reusable Prompt object:  
p = Prompt.new("A word with foo in it?") do  
&nbsp;&nbsp;validation /foo/i  
&nbsp;&nbsp;default "Foo"  
end  
p = Prompt.new("A word with foo in it?", "Foo", /foo/i)  
s = p.ask

---

<div class="post-metadata">

**Author:** ![Sean\_E\_McCardell](https://avatars.discourse-cdn.com/v4/letter/s/85f322/32.png) [@Sean\_E\_McCardell](https://rubytalk.org/u/Sean_E_McCardell)\
**Post date:** [27 April 2005 23:33 UTC](https://rubytalk.org/t/solution-highline-29/17855/4 "2005-04-27T23:33:17Z")

</div>

# This solution provides a framework for handling user input at a higher level  
# than "gets" and "chomp". Sorry to James for being so late...

> **···**
>
> #  
> # Usage:  
> #  
> # Define a class which inherits from HighLine::ValueInput,  
> # HighLine::ChoiceInput or HighLine::MenuInput, using the class methods  
> # (see below) to define the way the input will be handled. Then call the  
> # #ask class method to prompt for the input, passing additional definitions  
> # in an optional block (see examples at end of file).  
> #  
> #  
> # Class Methods (all classes):  
> #  
> # transform \<Proc object p\> [, \*args]  
> # Calls r = p.call(r, \*args), where r is the user's response  
> # transform symbol [, \*args]  
> # Calls r = r.method(symbol).call(\*args)  
> # transform :with\_my, symbol [, \*args]  
> # Calls r = self.method(symbol).call(r, \*args), where self is an instance  
> # of the class  
> #  
> # Transformations are applied to the input in the order in which they were  
> # defined, and they are inherited from parent classes cumulatively (is that  
> # a word?)  
> #  
> # synonym \<base string\>, \<synonym string\> [, \*\<synonym strings\>]  
> # Creates a transformation which maps each synonym string to the  
> # base string.  
> #  
> # okay\_if \<Proc object p\> [, \*args]  
> # Uses the return value of p.call(r, \*args) to determine if the validation  
> # phase can be skipped. (r is the user's response after transformation)  
> # okay\_if \<Regexp re\>  
> # As above, with the result of r =~ re  
> # okay\_if symbol [, \*args]  
> # As above, with the result of r.method(symbol).call(\*args)  
> # okay\_if :with\_my, symbol [, \*args]  
> # As above, with the result of self.method(symbol).call(r, \*args)  
> #  
> # Definitions for okay values are inherited.  
> #  
> # error\_message string  
> # Causes the string to be printed if the validation phase fails  
> # error\_message symbol  
> # As above, with the return value from self.method(symbol).call(r)  
> # error\_message \<Proc object p\>  
> # As above, with the return value from p.call(r)  
> #  
> # A class will only use one error\_message definition. See the section on  
> # validation below for more details about the difference in error handling  
> # between ValueInput and ChoiceInput/MenuInput.  
> #  
> #  
> # Class Methods (ValueInput):  
> #  
> # format\_hint string  
> # A string which is appended to the prompt. The default  
> # ValueInput#prompt\_suffix method wraps it in square brackets, e.g.  
> # "[YYYY-MM-DD]".  
> #  
> # Format hints are inherited, but not cumulatively--if a class provides one,  
> # it will override any hints in its parent classes.  
> #  
> # validate  
> #  
> # The syntax for the validate method is the same as that for okay\_if.  
> # However, procs and methods that it causes to be called have different  
> # return signatures:  
> # bool -\> indicates validity  
> # bool, str\_or\_nil -\> as above, plus an error message that overrides  
> # any error\_message definitions in the class  
> # bool, str\_or\_nil, val -\> as above, plus an alternate version of the  
> # response string  
> #  
> # When an alternate value is returned, subsequent validations that would  
> # have operated on the response string will operate on this alternate value  
> # instead (see the description of the #ask method below for details on using  
> # this value).  
> #  
> # Validators are cumulatively inherited, along with the error\_message  
> # defintions (so if a validator from a parent class fails, the error\_message  
> # from that class will be used).  
> #  
> # output\_format  
> #  
> # The syntax for output\_format is the same as that for transform. It provides  
> # a way to format the response after it has been validated. Like validators,  
> # output format procedures will operate on the response or on the alternate  
> # value if one is present.  
> #  
> #  
> # Class Methods (ChoiceInput, MenuInput):  
> #  
> # choices \*args  
> # Adds its arguments to the list of valid string responses for the class.  
> # The default ChoiceInput#prompt\_suffix method displays it like this--  
> # "[y/(n)/m]". The parentheses indicate the default answer (see #ask below).  
> #  
> # Instead of using validators, ChoiceInput classes simply check for a  
> # response that is in the list of choices.  
> #  
> # header string  
> # (MenuInput only) A string that is printed before the list of choices is  
> # displayed.  
> #  
> # items \*args  
> # (MenuInput only) The arguments are matched to the choices.  
> #  
> # When one of the choices is selected, the matching element in the list of  
> # items is returned as the alternate value. The default  
> # MenuInput#prompt\_suffix displays the header string, and one line for each  
> # choice in the format "#{choice[i]}\t#{item[i]}\n".  
> #  
> # The #ask method  
> #  
> # ask(prompt, default\_value=nil)  
> #  
> # When the ask method is called, things get done in this order:  
> # 1. The prompt is printed, with the result of #prompt\_suffix appended.  
> # 2. $stdin\_gets is called, and the raw input is saved.  
> # 3. All defined transformations are applied to the response.  
> # 4. If any okay\_if tests pass, the response is returned.  
> # 5. If the response is empty, and there is a default\_value, the default is  
> # returned.  
> # 6. Validation occurs. For ChoiceInput/MenuInput, this just involves  
> # matching the response to the list of choices. For ValueInput, all  
> # defined validation tests are run.  
> # 7. If validation succeeded, all output\_format rules are applied.  
> # 8. If validation failed, and an error message was returned, it is printed,  
> # and the process loops back to step 1.  
> #  
> # The return value is an object of class ResponseString, which is a subclass  
> # of normal String. It provides a #raw\_input method, an #error\_message method,  
> # a #valid? method, and and #alternate method, for getting details about the  
> # response.
> 
> module HighLine  
> &nbsp;&nbsp;  
> &nbsp;&nbsp;class BaseInput  
> &nbsp;&nbsp;&nbsp;&nbsp;  
> &nbsp;&nbsp;&nbsp;&nbsp;def self.get\_error\_message  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@em ||= nil  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;  
> &nbsp;&nbsp;&nbsp;&nbsp;def self.error\_message(em)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@em = em  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;  
> &nbsp;&nbsp;&nbsp;&nbsp;def self.get\_transformers  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@ts ||= []  
> &nbsp;&nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;&nbsp;def self.transform(\*ts)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@ts ||= []  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@ts \<\< ts  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;  
> &nbsp;&nbsp;&nbsp;&nbsp;def self.get\_okays  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@oi ||= []  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;  
> &nbsp;&nbsp;&nbsp;&nbsp;def self.okay\_if(\*oi)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@oi ||= []  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@oi \<\< oi  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;  
> &nbsp;&nbsp;&nbsp;&nbsp;def self.synonym(\*syns)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;default = syns[0]  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;synonyms = syns[1..-1]  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;transform :with\_my, :synonymize, synonyms, default  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;  
> &nbsp;&nbsp;&nbsp;&nbsp;def self.ask(prompt, default\_response=nil, &block)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;prompt = prompt + " " if prompt !~ /\s$/ and !prompt.empty?  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if block\_given?  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;klass = Class.new(self, &block)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;klass = self  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;inputter = klass.new(default\_response)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;while true  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print prompt  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$stdout.flush  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;response = inputter.gets  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break if response.valid?  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if response.error\_message  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;puts response.error\_message  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$stdout.flush  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return response  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;  
> &nbsp;&nbsp;&nbsp;&nbsp;def initialize(default\_response)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@default\_response = default\_response  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@klasses = []  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;klass = self.class  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;while klass.respond\_to? :get\_transformers  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@klasses.unshift(klass)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;klass = klass.superclass  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@okays = @klasses.collect { |klass|  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;curry\_okays(klass.get\_okays)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}.flatten  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@transformers = @klasses.collect { |klass|  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;curry\_transformers(klass.get\_transformers)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}.flatten  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;  
> &nbsp;&nbsp;&nbsp;&nbsp;def gets  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print prompt\_suffix  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$stdout.flush  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;raw\_input = $stdin.gets.chomp  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;response = raw\_input.dup  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@transformers.each do |transformer|  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;response = transformer.call(response)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if @default\_response and response.empty?  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return ResponseString.new(@default\_response, raw\_input, true)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end
> 
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@okays.each do |okay|  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if okay.call(response)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return ResponseString.new(response, raw\_input, true)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;do\_validate(response, raw\_input)  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;  
> &nbsp;&nbsp;&nbsp;&nbsp;def prompt\_suffix  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if @default\_response and !@default\_response.empty?  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"(#{@default\_response}) "  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;""  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;  
> &nbsp;&nbsp;&nbsp;&nbsp;def synonymize(r, synonyms, default)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return r unless synonyms.member? r  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;default  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;  
> &nbsp;&nbsp;&nbsp;&nbsp;def curry\_error\_message(klass\_error\_message)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;case klass\_error\_message  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;when Symbol  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;proc { |r| method(klass\_error\_message).call(r) }  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;when Proc  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;proc { |r| klass\_error\_message.call(r) }  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;when String  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;proc { |r| klass\_error\_message }  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;  
> &nbsp;&nbsp;&nbsp;&nbsp;def curry\_transformers(klass\_transformers)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;klass\_transformers.collect do |transformer|  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;p\_name = transformer[0]  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;args = transformer[1..-1]  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;case p\_name  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;when Symbol  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if p\_name == :with\_my  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;proc { |r| method(args[0]).call(r, \*args[1..-1]) }  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;proc { |r| r.method(p\_name).call(\*args) }  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;when Proc  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;proc { |r| p\_name.call(r, \*args) }  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;  
> &nbsp;&nbsp;&nbsp;&nbsp;def curry\_okays(klass\_okays)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;klass\_okays.collect { |okay|  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;p\_name = okay[0]  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;args = okay[1..-1]  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;case p\_name  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;when Symbol  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if p\_name == :with\_my  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;proc { |r| method(args[0]).call(r, \*args[1..-1]) }  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;proc { |r| r.method(p\_name).call(\*args) }  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;when Regexp  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;proc { |r| r.to\_s =~ p\_name }  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;when Proc  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;proc { |r| p\_name.call(r, \*args) }  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}.compact  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;  
> &nbsp;&nbsp;&nbsp;&nbsp;def do\_validate(response, raw\_input)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ResponseString.new(response, raw\_input, true)  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;class ValueInput \< BaseInput  
> &nbsp;&nbsp;  
> &nbsp;&nbsp;&nbsp;&nbsp;def self.get\_validators  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@vld ||= []  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;  
> &nbsp;&nbsp;&nbsp;&nbsp;def self.validate(\*vld)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@vld ||= []  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@vld \<\< vld  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;  
> &nbsp;&nbsp;&nbsp;&nbsp;def self.get\_output\_formats  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@of ||= []  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;  
> &nbsp;&nbsp;&nbsp;&nbsp;def self.output\_format(\*of)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@of ||= []  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@of \<\< of  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;  
> &nbsp;&nbsp;&nbsp;&nbsp;def self.get\_format\_hint  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@fh ||= nil  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;  
> &nbsp;&nbsp;&nbsp;&nbsp;def self.format\_hint(fh)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@fh = fh  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;  
> &nbsp;&nbsp;&nbsp;&nbsp;def initialize(default\_response)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;super  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;klasses = @klasses[1..-1]  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@validators = klasses.collect { |klass|  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;curry\_okays(klass.get\_validators)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@error\_messages = klasses.collect do |klass|  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;curry\_error\_message(klass.get\_error\_message)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@output\_formats = klasses.collect { |klass|  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;curry\_transformers(klass.get\_output\_formats)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}.flatten  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;klasses.reverse.each do |klass|  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@format\_hint = klass.get\_format\_hint  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break if @format\_hint  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;  
> &nbsp;&nbsp;&nbsp;&nbsp;def prompt\_suffix  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;super \<\< (@format\_hint ? "[#{@format\_hint}] " : "")  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;  
> &nbsp;&nbsp;&nbsp;&nbsp;def do\_validate(response, raw\_input)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;valid = true  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;err\_msg = nil  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@alternate = nil  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;resp = response  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@validators.each\_index do |i|  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@validators[i].each do |validator|  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;error\_message = nil  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;valid, err\_msg, alt = validator.call(resp)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if !valid and !err\_msg  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;while !@error\_messages[i]  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;i = i + 1  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break if i == @error\_messages.length  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;klass\_error\_message = @error\_messages[i]  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;err\_msg = klass\_error\_message.call(resp) if klass\_error\_message  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if alt  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;resp = alt  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@alternate = alt  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break unless valid  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break unless valid  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;resp = response if @output\_formats.empty?  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@output\_formats.each do |output\_format|  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;resp = output\_format.call(resp)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ResponseString.new(resp, raw\_input, valid, err\_msg, @alternate)  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;class ChoiceInput \< BaseInput  
> &nbsp;&nbsp;&nbsp;&nbsp;error\_message :default\_error\_message  
> &nbsp;&nbsp;&nbsp;&nbsp;  
> &nbsp;&nbsp;&nbsp;&nbsp;def self.get\_choices  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@cs ||= []  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;  
> &nbsp;&nbsp;&nbsp;&nbsp;def self.choices(\*cs)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if !get\_choices.empty?  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;raise SyntaxError, "cannot add multiple choice sets", caller  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@cs = cs  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;  
> &nbsp;&nbsp;&nbsp;&nbsp;def initialize(default\_response)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;super  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@choices = self.class.get\_choices  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@klasses.reverse.each do |klass|  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@error\_message = curry\_error\_message(klass.get\_error\_message)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break if @error\_message  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;  
> &nbsp;&nbsp;&nbsp;&nbsp;def wrap\_default(choice)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;choice == @default\_response ? "(#{choice})" : choice  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;  
> &nbsp;&nbsp;&nbsp;&nbsp;def prompt\_suffix  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"[" + @choices.collect { |ch| wrap\_default(ch) }.join('/') + "] "  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;  
> &nbsp;&nbsp;&nbsp;&nbsp;def default\_error\_message(response)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"Please enter one of #{@choices[0..-2].join(', ')} or #{@choices[-1]}"  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;  
> &nbsp;&nbsp;&nbsp;&nbsp;def do\_validate(response, raw\_input)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;error\_message = nil  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;valid = @choices.member? response  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if !valid  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;error\_message = @error\_message.call(response)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ResponseString.new(response, raw\_input, valid, error\_message)  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;end
> 
> &nbsp;&nbsp;class MenuInput \< ChoiceInput  
> &nbsp;&nbsp;  
> &nbsp;&nbsp;&nbsp;&nbsp;def self.get\_items  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@its ||= []  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;  
> &nbsp;&nbsp;&nbsp;&nbsp;def self.items(\*its)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@its ||= []  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if get\_choices.empty?  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;raise SyntaxError, "choices must be added before items", caller  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if its.length != @cs.length  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;raise SyntaxError, "number of items must match choices", caller  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@its = its  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;  
> &nbsp;&nbsp;&nbsp;&nbsp;def self.get\_header  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@hdr ||= nil  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;  
> &nbsp;&nbsp;&nbsp;&nbsp;def self.header(hdr)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@hdr = hdr  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;  
> &nbsp;&nbsp;&nbsp;&nbsp;def initialize(default\_response)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;super  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;klasses = @klasses[2..-1]  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@items = self.class.get\_items  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;klasses.reverse.each do |klass|  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@header = klass.get\_header  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break if @header  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;  
> &nbsp;&nbsp;&nbsp;&nbsp;def prompt\_suffix  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if @header  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ps = "#{@header}\n"  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ps = "\n"  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0.upto(@choices.length - 1) do |i|  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ps \<\< " " if @choices[i] != @default\_response  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ps \<\< wrap\_default(@choices[i]) + "\t" + @items[i] + "\n"  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return ps  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;  
> &nbsp;&nbsp;&nbsp;&nbsp;def default\_error\_message(response)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"Please select one of the given options"  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;  
> &nbsp;&nbsp;&nbsp;&nbsp;def do\_validate(response, raw\_input)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;rs = super  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;rs.alternate = @items[@choices.index(response)] if rs.valid?  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;rs  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;end  
> &nbsp;&nbsp;  
> &nbsp;&nbsp;class ResponseString \< String  
> &nbsp;&nbsp;&nbsp;&nbsp;attr\_accessor :alternate  
> &nbsp;&nbsp;&nbsp;&nbsp;attr\_reader :error\_message, :raw\_input  
> &nbsp;&nbsp;&nbsp;&nbsp;  
> &nbsp;&nbsp;&nbsp;&nbsp;def initialize(resp, raw, valid, err\_msg=nil, alt=nil)  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@raw\_input = raw  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@valid = valid  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@error\_message = err\_msg  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@alternate = alt || resp  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;super(resp)  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;&nbsp;&nbsp;  
> &nbsp;&nbsp;&nbsp;&nbsp;def valid?  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@valid  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;end  
> &nbsp;&nbsp;  
> end
> 
> if \_\_FILE\_\_ == $0
> 
> require 'date'
> 
> class IntegerInput \< HighLine::ValueInput  
> &nbsp;&nbsp;validate /^\d+$/  
> &nbsp;&nbsp;validate proc { |r| [true, nil, r.to\_i] }  
> end
> 
> puts IntegerInput.ask("Enter a number from 1 to 10, or Q to quit:") {  
> &nbsp;&nbsp;okay\_if /^q$/i  
> &nbsp;&nbsp;validate :between?, 1, 10  
> }
> 
> class DateInput \< HighLine::ValueInput  
> &nbsp;&nbsp;validate :with\_my, :check\_date  
> &nbsp;&nbsp;  
> &nbsp;&nbsp;def check\_date(r)  
> &nbsp;&nbsp;&nbsp;&nbsp;begin  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;test\_date = Date.parse(r)  
> &nbsp;&nbsp;&nbsp;&nbsp;rescue  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;false  
> &nbsp;&nbsp;&nbsp;&nbsp;else  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[true, nil, test\_date]  
> &nbsp;&nbsp;&nbsp;&nbsp;end  
> &nbsp;&nbsp;end  
> end
> 
> puts DateInput.ask("Enter a date:") {  
> &nbsp;&nbsp;output\_format :to\_s  
> &nbsp;&nbsp;error\_message "That is not a date!"  
> }
> 
> class YesOrNo \< HighLine::ChoiceInput  
> &nbsp;&nbsp;transform :downcase  
> &nbsp;&nbsp;choices "y", "n"  
> &nbsp;&nbsp;synonym "y", "yes", "oui", "si"  
> end
> 
> puts YesOrNo.ask("Is your computer turned on?", "y")
> 
> class EditorMenu \< HighLine::MenuInput  
> &nbsp;&nbsp;header "Please select an editor:"  
> &nbsp;&nbsp;choices "1", "2", "3"  
> &nbsp;&nbsp;items "vim", "vim", "vim"  
> &nbsp;&nbsp;error\_message "There are no other editors!"  
> end
> 
> puts EditorMenu.ask("", "1").alternate
> 
> end

---

<div class="post-metadata">

**Author:** ![James\_Edward\_Gray\_II](https://avatars.discourse-cdn.com/v4/letter/j/ea5d25/32.png) [@James\_Edward\_Gray\_II](https://rubytalk.org/u/James_Edward_Gray_II)\
**Post date:** [28 April 2005 02:52 UTC](https://rubytalk.org/t/solution-highline-29/17855/5 "2005-04-28T02:52:41Z")

</div>

> # This solution provides a framework for handling user input at a higher level  
> # than "gets" and "chomp".

Wow. I'm looking through this a bit to see what you've done here. Very impressive.

I feel pretty dumb for registering my solution with RubyForge today now. 😃

Any chance you could give us a few simple examples of usage? For example, how do the quiz examples translate to this system?

> Sorry to James for being so late...

I'm the one who should apologize. I finished up the summary earlier today. 😉

James Edward Gray II

> **···**
>
> On Apr 27, 2005, at 6:33 PM, Sean E. McCardell wrote:

---

<div class="post-metadata">

**Author:** ![Sean\_E\_McCardell](https://avatars.discourse-cdn.com/v4/letter/s/85f322/32.png) [@Sean\_E\_McCardell](https://rubytalk.org/u/Sean_E_McCardell)\
**Post date:** [28 April 2005 22:01 UTC](https://rubytalk.org/t/solution-highline-29/17855/6 "2005-04-28T22:01:15Z")

</div>

Sure thing. Here goes:

require 'highline'

# This might be useful for someone implementing COMMAND.COM in Ruby 🙂

class DiskError \< HighLine::ChoiceInput  
&nbsp;&nbsp;choices "abort", "retry", "fail"  
&nbsp;&nbsp;synonym "abort", "a"  
&nbsp;&nbsp;synonym "retry", "r"  
&nbsp;&nbsp;synonym "fail", "f"  
end

result = DiskError.ask("Error reading drive A:")

# And the output will look like:  
# Error reading drive A: [abort/retry/fail]  
# The user will continue to be prompted until "abort", "retry", or  
# "fail" is entered (or one of their synonyms, "a", "r", or "f"

# For the age example from the quiz, I would do:

class IntegerInput \< HighLine::ValueInput  
&nbsp;&nbsp;validate /^\d+$/  
&nbsp;&nbsp;# when a validation procedure returns a three-element array,  
&nbsp;&nbsp;# the second element can be an error message, and the third  
&nbsp;&nbsp;# element will be used as an alternate test value (instead of the  
&nbsp;&nbsp;# user's response string) for subsequent validation tests.  
&nbsp;&nbsp;validate proc { |r| [true, nil, r.to\_i] }  
end

age = IntegerInput.ask("Enter your age:") {  
&nbsp;&nbsp;validate :between?, 0, 105  
}.alternate

# The #alternate method of the returned object gives you access to  
# the alternate test value, if any, created during validation. In this  
# case, it is an Integer

# And for an indirect way of finding an age, here's one that demonstrates  
# using an instance method for validation:

require 'date'  
class DateInput \< HighLine::ValueInput  
&nbsp;&nbsp;validate :with\_my, :ensure\_date

&nbsp;&nbsp;def ensure\_date(response)  
&nbsp;&nbsp;&nbsp;&nbsp;begin  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;test\_date = Date.parse(response)  
&nbsp;&nbsp;&nbsp;&nbsp;rescue ArgumentError  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;false  
&nbsp;&nbsp;&nbsp;&nbsp;else  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[true, nil, test\_date]  
&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;end  
end

birthday = DateInput.ask("When were you born?") {  
&nbsp;&nbsp;# output\_format, like validate, operates on an alternate test value  
&nbsp;&nbsp;# if one was created during validation. This just calls #to\_s on that  
&nbsp;&nbsp;# value, so you always get anwers in the form "YYYY-MM-DD", even if  
&nbsp;&nbsp;# you enter something like "April 28th, 2005"  
&nbsp;&nbsp;output\_format :to\_s  
&nbsp;&nbsp;error\_message "Please enter a valid date"  
&nbsp;&nbsp;validate proc { |r| [r \<= Date.today, "You can't be from the future!"] }  
}

Hope this helps,

--Sean

> **···**
>
> On 11:52 Thu 28 Apr , James Edward Gray II wrote:
> 
> > Any chance you could give us a few simple examples of usage? For  
> > example, how do the quiz examples translate to this system?

---

<div class="post-metadata">

**Author:** ![James\_Edward\_Gray\_II](https://avatars.discourse-cdn.com/v4/letter/j/ea5d25/32.png) [@James\_Edward\_Gray\_II](https://rubytalk.org/u/James_Edward_Gray_II)\
**Post date:** [29 April 2005 14:36 UTC](https://rubytalk.org/t/solution-highline-29/17855/7 "2005-04-29T14:36:22Z")

</div>

> > Any chance you could give us a few simple examples of usage? For  
> > example, how do the quiz examples translate to this system?
> 
> Sure thing. Here goes:

[snip great examples]

> Hope this helps,

It was impressive. Thanks for sharing!

James Edward Gray II

> **···**
>
> On Apr 28, 2005, at 5:01 PM, Sean E. McCardell wrote:
> 
> > On 11:52 Thu 28 Apr , James Edward Gray II wrote:
