highline 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +30 -0
- data/INSTALL +10 -0
- data/LICENSE +7 -0
- data/README +31 -0
- data/Rakefile +52 -0
- data/TODO +16 -0
- data/examples/basic_usage.rb +51 -0
- data/lib/highline.rb +125 -0
- data/lib/highline/import.rb +25 -0
- data/lib/highline/question.rb +208 -0
- data/test/tc_highline.rb +301 -0
- data/test/tc_import.rb +28 -0
- data/test/ts_all.rb +14 -0
- metadata +61 -0
data/CHANGELOG
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
= Change Log
|
2
|
+
|
3
|
+
Below is a complete listing of changes for each revision of HighLine.
|
4
|
+
|
5
|
+
== 0.2.0
|
6
|
+
|
7
|
+
* Added Unit Tests to cover an already fixed output bug in the future.
|
8
|
+
* Added Rakefile and setup test action (default).
|
9
|
+
* Renamed HighLine::Answer to HighLine::Question to better illustrate its role.
|
10
|
+
* Renamed fetch_line() to get_response() to better define its goal.
|
11
|
+
* Simplified explain_error in terms of the Question object.
|
12
|
+
* Renamed accept?() to in_range?() to better define purpose.
|
13
|
+
* Reworked valid?() into valid_answer?() to better fit Question object.
|
14
|
+
* Reworked <tt>@member</tt> into <tt>@in</tt>, to make it easier to remember and
|
15
|
+
switched implementation to include?().
|
16
|
+
* Added range checks for @above and @below.
|
17
|
+
* Fixed the bug causing ask() to swallow NoMethodErrors.
|
18
|
+
* Rolled ask_on_error() into responses.
|
19
|
+
* Redirected imports to Kernel from Object.
|
20
|
+
* Added support for <tt>validate lambda { ... }</tt>.
|
21
|
+
* Added default answer support.
|
22
|
+
* Fixed bug that caused ask() to die with an empty question.
|
23
|
+
* Added complete documentation.
|
24
|
+
* Improve the implemetation of agree() to be the intended "yes" or "no" only
|
25
|
+
question.
|
26
|
+
|
27
|
+
== 0.1.0
|
28
|
+
|
29
|
+
* Initial release as the solution to
|
30
|
+
{Ruby Quiz #29}[http://www.rubyquiz.com/quiz29.html].
|
data/INSTALL
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
= Installing the Gem
|
2
|
+
|
3
|
+
HighLine is intended to be installed via the
|
4
|
+
RubyGems[http://rubyforge.org/projects/rubygems/] system. To get the latest
|
5
|
+
version, simply enter the following into your command prompt:
|
6
|
+
|
7
|
+
sudo gem install highline
|
8
|
+
|
9
|
+
You must have RubyGems[http://rubyforge.org/projects/rubygems/] installed for
|
10
|
+
the above to work.
|
data/LICENSE
ADDED
data/README
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
= Read Me
|
2
|
+
|
3
|
+
by James Edward Gray II
|
4
|
+
|
5
|
+
== Description
|
6
|
+
|
7
|
+
Welcome to HighLine.
|
8
|
+
|
9
|
+
This library was designed to ease the tedious tasks of doing console input and
|
10
|
+
output with low-level methods like gets() and puts(). This library provides a
|
11
|
+
robust system to requesting data from a user, without needing to code all the
|
12
|
+
error checking and validation rules and without needing to convert the typed
|
13
|
+
Strings into what your program really needs. Just tell HighLine what you're
|
14
|
+
after, and leg it do all the leg work.
|
15
|
+
|
16
|
+
== Documentation
|
17
|
+
|
18
|
+
See HighLine and HighLine::Question for documentation.
|
19
|
+
|
20
|
+
== Examples
|
21
|
+
|
22
|
+
See the examples/ directory of this project for code examples.
|
23
|
+
|
24
|
+
== Installing
|
25
|
+
|
26
|
+
See the INSTALL file for instructions.
|
27
|
+
|
28
|
+
== Questions
|
29
|
+
|
30
|
+
Feel free to email {James Edward Gray II}[mailto:james@grayproductions.net] with
|
31
|
+
any questions.
|
data/Rakefile
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require "rake/rdoctask"
|
2
|
+
require "rake/testtask"
|
3
|
+
require "rake/gempackagetask"
|
4
|
+
|
5
|
+
require "rubygems"
|
6
|
+
|
7
|
+
task :default => [:test]
|
8
|
+
|
9
|
+
Rake::TestTask.new do |test|
|
10
|
+
test.libs << "test"
|
11
|
+
test.test_files = [ "test/ts_all.rb" ]
|
12
|
+
test.verbose = true
|
13
|
+
end
|
14
|
+
|
15
|
+
Rake::RDocTask.new do |rdoc|
|
16
|
+
rdoc.main = "README"
|
17
|
+
rdoc.rdoc_files.include( "README", "INSTALL",
|
18
|
+
"TODO", "CHANGELOG",
|
19
|
+
"LICENSE", "lib/" )
|
20
|
+
rdoc.rdoc_dir = "doc/html"
|
21
|
+
rdoc.title = "HighLine Documentation"
|
22
|
+
end
|
23
|
+
|
24
|
+
spec = Gem::Specification.new do |spec|
|
25
|
+
spec.name = "highline"
|
26
|
+
spec.version = "0.2.0"
|
27
|
+
spec.platform = Gem::Platform::RUBY
|
28
|
+
spec.summary = "HighLine is a high-level line oriented console interface."
|
29
|
+
spec.files = Dir.glob("{examples,lib,test}/**/*.rb").
|
30
|
+
delete_if { |item| item.include?("CVS") } + ["Rakefile"]
|
31
|
+
spec.test_suite_file = "test/ts_all.rb"
|
32
|
+
spec.has_rdoc = true
|
33
|
+
spec.extra_rdoc_files = %w{README INSTALL TODO CHANGELOG LICENSE}
|
34
|
+
spec.rdoc_options << '--title' << 'HighLine Documentation' <<
|
35
|
+
'--main' << 'README'
|
36
|
+
spec.require_path = 'lib'
|
37
|
+
spec.autorequire = "highline"
|
38
|
+
spec.author = "James Edward Gray II"
|
39
|
+
spec.email = "james@grayproductions.net"
|
40
|
+
spec.rubyforge_project = "highline"
|
41
|
+
spec.homepage = "http://highline.rubyforge.org"
|
42
|
+
spec.description = <<END_DESC
|
43
|
+
A "high-level line oriented" input/output library that grew out of my solution
|
44
|
+
to Ruby Quiz #29. This library attempts to make standard console input and
|
45
|
+
output robust and painless.
|
46
|
+
END_DESC
|
47
|
+
end
|
48
|
+
|
49
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
50
|
+
pkg.need_zip = true
|
51
|
+
pkg.need_tar = true
|
52
|
+
end
|
data/TODO
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
= To Do List
|
2
|
+
|
3
|
+
The following is a list of planned expansions for HighLine, in no particular
|
4
|
+
order.
|
5
|
+
|
6
|
+
* Expose response replacements for user code strings.
|
7
|
+
* Support character oriented input.
|
8
|
+
* Support <tt>ask(..., Array)</tt>, better than the current
|
9
|
+
<tt>ask(..., lambda { |arr| arr.split(",") })</tt> or similar.
|
10
|
+
* Support <tt>ask(..., Hash)</tt>.
|
11
|
+
* Provide for <tt>ask(..., MyClass)</tt>.
|
12
|
+
* Add ANSI color support to say().
|
13
|
+
* Add word wrap support to say().
|
14
|
+
* Allow user code to specify whitespace handling for <tt>@input</tt>
|
15
|
+
(chomp(), strip(), etc.).
|
16
|
+
* Support install with setup.rb.
|
@@ -0,0 +1,51 @@
|
|
1
|
+
#!/usr/local/bin/ruby -w
|
2
|
+
|
3
|
+
# basic_usage.rb
|
4
|
+
#
|
5
|
+
# Created by James Edward Gray II on 2005-04-28.
|
6
|
+
# Copyright 2005 Gray Productions. All rights reserved.
|
7
|
+
|
8
|
+
require "rubygems"
|
9
|
+
require "highline/import"
|
10
|
+
require "yaml"
|
11
|
+
|
12
|
+
contacts = [ ]
|
13
|
+
|
14
|
+
begin
|
15
|
+
entry = Hash.new
|
16
|
+
|
17
|
+
# basic output
|
18
|
+
say("Enter a contact:")
|
19
|
+
|
20
|
+
# basic input
|
21
|
+
entry[:name] = ask("Name? (last, first) ") do |q|
|
22
|
+
q.validate = /\A\w+, ?\w+\Z/
|
23
|
+
end
|
24
|
+
entry[:company] = ask("Company? ") { |q| q.default = "none" }
|
25
|
+
entry[:address] = ask("Address? ")
|
26
|
+
entry[:city] = ask("City? ")
|
27
|
+
entry[:state] = ask("State? ") { |q| q.validate = /\A[A-Z]{2}\Z/ }
|
28
|
+
entry[:zip] = ask("Zip? ") do |q|
|
29
|
+
q.validate = /\A\d{5}(?:-?\d{4})?\Z/
|
30
|
+
end
|
31
|
+
entry[:phone] = ask( "Phone? ",
|
32
|
+
lambda { |p| p.delete("^0-9").
|
33
|
+
sub(/\A(\d{3})/, '(\1) ').
|
34
|
+
sub(/(\d{4})\Z/, '-\1') } ) do |q|
|
35
|
+
q.validate = lambda { |p| p.delete("^0-9").length == 10 }
|
36
|
+
q.responses[:not_valid] = "Enter a phone numer with area code."
|
37
|
+
end
|
38
|
+
entry[:age] = ask("Age? ", Integer) { |q| q.in = 0..105 }
|
39
|
+
entry[:birthday] = ask("Birthday? ", Date)
|
40
|
+
entry[:interests] = ask( "Interests? (comma separated list) ",
|
41
|
+
lambda { |str| str.split(/,\s*/) } )
|
42
|
+
entry[:description] = ask("Enter a description for this contact.")
|
43
|
+
|
44
|
+
contacts << entry
|
45
|
+
# shortcut for yes and no questions
|
46
|
+
end while agree("Enter another contact? ")
|
47
|
+
|
48
|
+
if agree("Save these contacts? ")
|
49
|
+
file_name = ask("Enter a file name: ") { |q| q.validate = /\A\w+\Z/ }
|
50
|
+
File.open("#{file_name}.yaml", "w") { |file| YAML.dump(contacts, file) }
|
51
|
+
end
|
data/lib/highline.rb
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
#!/usr/local/bin/ruby -w
|
2
|
+
|
3
|
+
# highline.rb
|
4
|
+
#
|
5
|
+
# Created by James Edward Gray II on 2005-04-26.
|
6
|
+
# Copyright 2005 Gray Productions. All rights reserved.
|
7
|
+
#
|
8
|
+
# See HighLine for documentation.
|
9
|
+
|
10
|
+
require "highline/question"
|
11
|
+
|
12
|
+
#
|
13
|
+
# A HighLine object is a "high-level line oriented" shell over an input and an
|
14
|
+
# output stream. HighLine simplifies common console interaction, effectively
|
15
|
+
# replacing puts() and gets(). User code can simply specify the question to ask
|
16
|
+
# and any details about user interaction, then leave the rest of the work to
|
17
|
+
# HighLine. When HighLine.ask() returns, you'll have to answer you requested,
|
18
|
+
# even if HighLine had to ask many times, validate results, perform range
|
19
|
+
# checking, convert types, etc.
|
20
|
+
#
|
21
|
+
class HighLine
|
22
|
+
# An internal HighLine error. User code does not need to trap this.
|
23
|
+
class QuestionError < StandardError
|
24
|
+
# do nothing, just creating a unique error type
|
25
|
+
end
|
26
|
+
|
27
|
+
# Create an instance of HighLine, connected to the streams _input_
|
28
|
+
# and _output_.
|
29
|
+
def initialize( input = $stdin, output = $stdout )
|
30
|
+
@input = input
|
31
|
+
@output = output
|
32
|
+
end
|
33
|
+
|
34
|
+
#
|
35
|
+
# A shortcut to HighLine.ask() a question that only accepts "yes" or "no"
|
36
|
+
# answers ("y" and "n" are allowed) and returns +true+ or +false+
|
37
|
+
# (+true+ for "yes").
|
38
|
+
#
|
39
|
+
def agree( yes_or_no_question )
|
40
|
+
ask(yes_or_no_question, lambda { |yn| yn.downcase[0] == ?y}) do |q|
|
41
|
+
q.validate = /\Ay(?:es)?|no?\Z/i
|
42
|
+
q.responses[:not_valid] = 'Please enter "yes" or "no".'
|
43
|
+
q.responses[:ask_on_error] = :question
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
#
|
48
|
+
# This method is the primary interface for user input. Just provide a
|
49
|
+
# _question_ to ask the user, the _answer_type_ you want returned, and
|
50
|
+
# optionally a code block setting up details of how you want the question
|
51
|
+
# handled. See HighLine.say() for details on the format of _question_, and
|
52
|
+
# HighLine::Question for more information about _answer_type_ and what's
|
53
|
+
# valid in the code block.
|
54
|
+
#
|
55
|
+
def ask( question, answer_type = String, &details ) # :yields: question
|
56
|
+
question = Question.new(question, answer_type, &details)
|
57
|
+
|
58
|
+
say(question)
|
59
|
+
begin
|
60
|
+
answer = question.answer_or_default(get_response)
|
61
|
+
unless question.valid_answer?(answer)
|
62
|
+
explain_error(question, :not_valid)
|
63
|
+
raise QuestionError
|
64
|
+
end
|
65
|
+
|
66
|
+
answer = question.convert(answer)
|
67
|
+
|
68
|
+
if question.in_range?(answer)
|
69
|
+
answer
|
70
|
+
else
|
71
|
+
explain_error(question, :not_in_range)
|
72
|
+
raise QuestionError
|
73
|
+
end
|
74
|
+
rescue QuestionError
|
75
|
+
retry
|
76
|
+
rescue ArgumentError
|
77
|
+
explain_error(question, :invalid_type)
|
78
|
+
retry
|
79
|
+
rescue NameError
|
80
|
+
raise if $!.is_a?(NoMethodError)
|
81
|
+
explain_error(question, :ambiguous_completion)
|
82
|
+
retry
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
#
|
87
|
+
# The basic output method for HighLine objects. If the provided _statement_
|
88
|
+
# ends with a space or tab character, a newline will not be appended (output
|
89
|
+
# will be flush()ed). All other cases are passed straight to Kernel.puts().
|
90
|
+
#
|
91
|
+
def say( statement )
|
92
|
+
statement = statement.to_s
|
93
|
+
return unless statement.length > 0
|
94
|
+
|
95
|
+
if statement[-1, 1] == " " or statement[-1, 1] == "\t"
|
96
|
+
@output.print(statement)
|
97
|
+
@output.flush
|
98
|
+
else
|
99
|
+
@output.puts(statement)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
#
|
106
|
+
# A helper method for sending the output stream and error and repeat
|
107
|
+
# of the question.
|
108
|
+
#
|
109
|
+
def explain_error( question, error )
|
110
|
+
say(question.responses[error])
|
111
|
+
if question.responses[:ask_on_error] == :question
|
112
|
+
say(question)
|
113
|
+
elsif question.responses[:ask_on_error]
|
114
|
+
say(question.responses[:ask_on_error])
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
#
|
119
|
+
# Read a line of input from the input stream, chomp()ing any newline
|
120
|
+
# characters.
|
121
|
+
#
|
122
|
+
def get_response( )
|
123
|
+
@input.gets.chomp
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/usr/local/bin/ruby -w
|
2
|
+
|
3
|
+
# import.rb
|
4
|
+
#
|
5
|
+
# Created by James Edward Gray II on 2005-04-26.
|
6
|
+
# Copyright 2005 Gray Productions. All rights reserved.
|
7
|
+
|
8
|
+
require "highline"
|
9
|
+
require "forwardable"
|
10
|
+
|
11
|
+
$terminal = HighLine.new
|
12
|
+
|
13
|
+
#
|
14
|
+
# <tt>require "highline/import"</tt> adds shorcut methods to Kernel, making
|
15
|
+
# agree(), ask(), and say() globally available. This is handy for quick and
|
16
|
+
# dirty input and output. These methods use the HighLine object in the global
|
17
|
+
# variable <tt>$terminal</tt>, which is initialized to used <tt>$stdin</tt> and
|
18
|
+
# <tt>$stdout</tt> (you are free to change this). Otherwise, these methods are
|
19
|
+
# identical to their HighLine counterparts, see that class for detailed
|
20
|
+
# explinations.
|
21
|
+
#
|
22
|
+
module Kernel
|
23
|
+
extend Forwardable
|
24
|
+
def_delegators :$terminal, :agree, :ask, :say
|
25
|
+
end
|
@@ -0,0 +1,208 @@
|
|
1
|
+
#!/usr/local/bin/ruby -w
|
2
|
+
|
3
|
+
# question.rb
|
4
|
+
#
|
5
|
+
# Created by James Edward Gray II on 2005-04-26.
|
6
|
+
# Copyright 2005 Gray Productions. All rights reserved.
|
7
|
+
|
8
|
+
require "optparse"
|
9
|
+
require "date"
|
10
|
+
|
11
|
+
class HighLine
|
12
|
+
#
|
13
|
+
# Question objects contain all the details of a single invocation of
|
14
|
+
# HighLine.ask(). The object is initialized by the parameters passed to
|
15
|
+
# HighLine.ask() and then queried to make sure each step of the input
|
16
|
+
# process is handled according to the users wishes.
|
17
|
+
#
|
18
|
+
class Question
|
19
|
+
#
|
20
|
+
# Create an instance of HighLine::Question. Expects a _question_ to ask
|
21
|
+
# (can be <tt>""</tt>) and an _answer_type_ to convert the answer to.
|
22
|
+
# The _answer_type_ parameter must be a type recongnized by
|
23
|
+
# Question.convert(). If given, a block is yeilded the new Question
|
24
|
+
# object to allow custom initializaion.
|
25
|
+
#
|
26
|
+
def initialize( question, answer_type )
|
27
|
+
# initialize instance data
|
28
|
+
@question = question
|
29
|
+
@answer_type = answer_type
|
30
|
+
|
31
|
+
@default = nil
|
32
|
+
@validate = nil
|
33
|
+
@above = nil
|
34
|
+
@below = nil
|
35
|
+
@in = nil
|
36
|
+
@responses = Hash.new
|
37
|
+
|
38
|
+
# allow block to override settings
|
39
|
+
yield self if block_given?
|
40
|
+
|
41
|
+
# finalize responses based on settings
|
42
|
+
append_default unless default.nil?
|
43
|
+
@responses = { :ambiguous_completion =>
|
44
|
+
"Ambiguous choice. " +
|
45
|
+
"Please choose one of #{@answer_type.inspect}.",
|
46
|
+
:ask_on_error =>
|
47
|
+
"? ",
|
48
|
+
:invalid_type =>
|
49
|
+
"You must enter a valid #{@answer_type}.",
|
50
|
+
:not_in_range =>
|
51
|
+
"Your answer isn't within the expected range " +
|
52
|
+
"(#{expected_range}).",
|
53
|
+
:not_valid =>
|
54
|
+
"Your answer isn't valid (must match " +
|
55
|
+
"#{@validate.inspect})." }.merge(@responses)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Used to provide a default answer to this question.
|
59
|
+
attr_accessor :default
|
60
|
+
#
|
61
|
+
# If set to a Regexp, the answer must match (before type conversion).
|
62
|
+
# Can also be set to a Proc which will be called with the provided
|
63
|
+
# answer to validate with a +true+ or +false+ return.
|
64
|
+
#
|
65
|
+
attr_accessor :validate
|
66
|
+
# Used to control range checks for answer.
|
67
|
+
attr_accessor :above, :below
|
68
|
+
# If set, answer must pass an include?() check on this object.
|
69
|
+
attr_accessor :in
|
70
|
+
#
|
71
|
+
# A Hash that stores the various responses used by HighLine to notify
|
72
|
+
# the user. The currently used responses and their purpose are as
|
73
|
+
# follows:
|
74
|
+
#
|
75
|
+
# <tt>:ambiguous_completion</tt>:: Used to notify the user of an
|
76
|
+
# ambiguous answer the auto-completion
|
77
|
+
# system cannot resolve.
|
78
|
+
# <tt>:ask_on_error</tt>:: This is the question that will be
|
79
|
+
# redisplayed to the user in the event
|
80
|
+
# of an error. Can be set to
|
81
|
+
# <tt>:question</tt> to repeat the
|
82
|
+
# original question.
|
83
|
+
# <tt>:invalid_type</tt>:: The error message shown when a type
|
84
|
+
# conversion fails.
|
85
|
+
# <tt>:not_in_range</tt>:: Used to notify the user that a
|
86
|
+
# provided answer did not satisfy
|
87
|
+
# the range requirement tests.
|
88
|
+
# <tt>:not_valid</tt>:: The error message shown when
|
89
|
+
# validation checks fail.
|
90
|
+
#
|
91
|
+
attr_reader :responses
|
92
|
+
|
93
|
+
#
|
94
|
+
# Transforms the given _answer_string_ into the expected type for this
|
95
|
+
# Question. Currently supported conversions are:
|
96
|
+
#
|
97
|
+
# <tt>[...]</tt>:: Answer must be a member of the passed Array.
|
98
|
+
# Auto-completion is used to expand partial
|
99
|
+
# answers.
|
100
|
+
# <tt>lambda {...}</tt>:: Answer is passed to lambda for conversion.
|
101
|
+
# Date:: Date.parse() is called with answer.
|
102
|
+
# DateTime:: DateTime.parse() is called with answer.
|
103
|
+
# Float:: Answer is converted with Kernel.Float().
|
104
|
+
# Integer:: Answer is converted with Kernel.Integer().
|
105
|
+
# +nil+:: Answer is left in String format.
|
106
|
+
# String:: Answer is converted with Kernel.String().
|
107
|
+
# Regexp:: Answer is fed to Regexp.new().
|
108
|
+
# Symbol:: The method to_sym() is called on answer and
|
109
|
+
# the result returned.
|
110
|
+
#
|
111
|
+
# This method throws ArgumentError, if the conversion cannot be
|
112
|
+
# completed for any reason.
|
113
|
+
#
|
114
|
+
def convert( answer_string )
|
115
|
+
if @answer_type.nil?
|
116
|
+
answer_string
|
117
|
+
elsif [Float, Integer, String].include?(@answer_type)
|
118
|
+
Kernel.send(@answer_type.to_s.to_sym, answer_string)
|
119
|
+
elsif @answer_type == Symbol
|
120
|
+
answer_string.to_sym
|
121
|
+
elsif @answer_type == Regexp
|
122
|
+
Regexp.new(answer_string)
|
123
|
+
elsif @answer_type.is_a?(Array)
|
124
|
+
# cheating, using OptionParser's Completion module
|
125
|
+
@answer_type.extend(OptionParser::Completion)
|
126
|
+
@answer_type.complete(answer_string).last
|
127
|
+
elsif [Date, DateTime].include?(@answer_type)
|
128
|
+
@answer_type.parse(answer_string)
|
129
|
+
elsif @answer_type.is_a?(Proc)
|
130
|
+
@answer_type[answer_string]
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
#
|
135
|
+
# Returns the provided _answer_string_ or the default answer for this
|
136
|
+
# Question if a default was set and the answer is empty.
|
137
|
+
#
|
138
|
+
def answer_or_default( answer_string )
|
139
|
+
if answer_string.length == 0 and not @default.nil?
|
140
|
+
@default
|
141
|
+
else
|
142
|
+
answer_string
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
#
|
147
|
+
# Returns +true+ if the _answer_object_ is greater than the _above_
|
148
|
+
# attribute, less than the _below_ attribute and included?()ed in the
|
149
|
+
# _in_ attribute. Otherwise, +false+ is returned. Any +nil+ attributes
|
150
|
+
# are not checked.
|
151
|
+
#
|
152
|
+
def in_range?( answer_object )
|
153
|
+
(@above.nil? or answer_object > @above) and
|
154
|
+
(@below.nil? or answer_object < @below) and
|
155
|
+
(@in.nil? or @in.include?(answer_object))
|
156
|
+
end
|
157
|
+
|
158
|
+
# Stringifies the question to be asked.
|
159
|
+
def to_s( )
|
160
|
+
@question
|
161
|
+
end
|
162
|
+
|
163
|
+
#
|
164
|
+
# Returns +true+ if the provided _answer_string_ is accepted by the
|
165
|
+
# _validate_ attribute or +false+ if it's not.
|
166
|
+
#
|
167
|
+
def valid_answer?( answer_string )
|
168
|
+
@validate.nil? or
|
169
|
+
(@validate.is_a?(Regexp) and answer_string =~ @validate) or
|
170
|
+
(@validate.is_a?(Proc) and @validate[answer_string])
|
171
|
+
end
|
172
|
+
|
173
|
+
private
|
174
|
+
|
175
|
+
#
|
176
|
+
# Adds the default choice to the end of question between <tt>|...|</tt>.
|
177
|
+
# Trailing whitespace is preserved so the function of HighLine.say() is
|
178
|
+
# not affected.
|
179
|
+
#
|
180
|
+
def append_default( )
|
181
|
+
if @question =~ /([\t ]+)\Z/
|
182
|
+
@question << "|#{@default}|#{$1}"
|
183
|
+
elsif @question == ""
|
184
|
+
@question << "|#{@default}| "
|
185
|
+
elsif @question[-1, 1] == "\n"
|
186
|
+
@question[-2, 0] = " |#{@default}|"
|
187
|
+
else
|
188
|
+
@question << " |#{@default}|"
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# Returns a english explination of the current range settings.
|
193
|
+
def expected_range( )
|
194
|
+
expected = [ ]
|
195
|
+
|
196
|
+
expected << "above #{@above}" unless @above.nil?
|
197
|
+
expected << "below #{@below}" unless @below.nil?
|
198
|
+
expected << "included in #{@in.inspect}" unless @in.nil?
|
199
|
+
|
200
|
+
case expected.size
|
201
|
+
when 0 then ""
|
202
|
+
when 1 then expected.first
|
203
|
+
when 2 then expected.join(" and ")
|
204
|
+
else expected[0..-2].join(", ") + ", and #{expected.last}"
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
data/test/tc_highline.rb
ADDED
@@ -0,0 +1,301 @@
|
|
1
|
+
#!/usr/local/bin/ruby -w
|
2
|
+
|
3
|
+
# tc_highline.rb
|
4
|
+
#
|
5
|
+
# Created by James Edward Gray II on 2005-04-26.
|
6
|
+
# Copyright 2005 Gray Productions. All rights reserved.
|
7
|
+
|
8
|
+
$test_lib_dir ||= File.join(File.dirname(__FILE__), "..", "lib")
|
9
|
+
$:.unshift($test_lib_dir) unless $:.include?($test_lib_dir)
|
10
|
+
|
11
|
+
require "test/unit"
|
12
|
+
|
13
|
+
require "highline"
|
14
|
+
require "stringio"
|
15
|
+
|
16
|
+
class TestHighLine < Test::Unit::TestCase
|
17
|
+
def setup
|
18
|
+
@input = StringIO.new
|
19
|
+
@output = StringIO.new
|
20
|
+
@terminal = HighLine.new(@input, @output)
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_agree
|
24
|
+
@input << "y\nyes\nYES\nHell no!\nNo\n"
|
25
|
+
@input.rewind
|
26
|
+
|
27
|
+
assert_equal(true, @terminal.agree("Yes or no? "))
|
28
|
+
assert_equal(true, @terminal.agree("Yes or no? "))
|
29
|
+
assert_equal(true, @terminal.agree("Yes or no? "))
|
30
|
+
assert_equal(false, @terminal.agree("Yes or no? "))
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_ask
|
34
|
+
name = "James Edward Gray II"
|
35
|
+
@input << name << "\n"
|
36
|
+
@input.rewind
|
37
|
+
|
38
|
+
assert_equal(name, @terminal.ask("What is your name? "))
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_defaults
|
42
|
+
@input << "\nNo Comment\n"
|
43
|
+
@input.rewind
|
44
|
+
|
45
|
+
answer = @terminal.ask("Are you sexually active? ") do |q|
|
46
|
+
q.validate = /\Ay(?:es)?|no?|no comment\Z/i
|
47
|
+
end
|
48
|
+
assert_equal("No Comment", answer)
|
49
|
+
|
50
|
+
@input.truncate(@input.rewind)
|
51
|
+
@input << "\nYes\n"
|
52
|
+
@input.rewind
|
53
|
+
@output.truncate(@output.rewind)
|
54
|
+
|
55
|
+
answer = @terminal.ask("Are you sexually active? ") do |q|
|
56
|
+
q.default = "No Comment"
|
57
|
+
q.validate = /\Ay(?:es)?|no?|no comment\Z/i
|
58
|
+
end
|
59
|
+
assert_equal("No Comment", answer)
|
60
|
+
assert_equal( "Are you sexually active? |No Comment| ",
|
61
|
+
@output.string )
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_empty
|
65
|
+
@input << "\n"
|
66
|
+
@input.rewind
|
67
|
+
|
68
|
+
answer = @terminal.ask("") do |q|
|
69
|
+
q.default = "yes"
|
70
|
+
q.validate = /\Ay(?:es)?|no?\Z/i
|
71
|
+
end
|
72
|
+
assert_equal("yes", answer)
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_range_requirements
|
76
|
+
@input << "112\n-541\n28\n"
|
77
|
+
@input.rewind
|
78
|
+
|
79
|
+
answer = @terminal.ask("Tell me your age.", Integer) do |q|
|
80
|
+
q.in = 0..105
|
81
|
+
end
|
82
|
+
assert_equal(28, answer)
|
83
|
+
assert_equal( "Tell me your age.\n" +
|
84
|
+
"Your answer isn't within the expected range " +
|
85
|
+
"(included in 0..105).\n" +
|
86
|
+
"? " +
|
87
|
+
"Your answer isn't within the expected range " +
|
88
|
+
"(included in 0..105).\n" +
|
89
|
+
"? ", @output.string )
|
90
|
+
|
91
|
+
@input.truncate(@input.rewind)
|
92
|
+
@input << "1\n-541\n28\n"
|
93
|
+
@input.rewind
|
94
|
+
@output.truncate(@output.rewind)
|
95
|
+
|
96
|
+
answer = @terminal.ask("Tell me your age.", Integer) do |q|
|
97
|
+
q.above = 3
|
98
|
+
end
|
99
|
+
assert_equal(28, answer)
|
100
|
+
assert_equal( "Tell me your age.\n" +
|
101
|
+
"Your answer isn't within the expected range " +
|
102
|
+
"(above 3).\n" +
|
103
|
+
"? " +
|
104
|
+
"Your answer isn't within the expected range " +
|
105
|
+
"(above 3).\n" +
|
106
|
+
"? ", @output.string )
|
107
|
+
|
108
|
+
@input.truncate(@input.rewind)
|
109
|
+
@input << "1\n28\n-541\n"
|
110
|
+
@input.rewind
|
111
|
+
@output.truncate(@output.rewind)
|
112
|
+
|
113
|
+
answer = @terminal.ask("Lowest numer you can think of?", Integer) do |q|
|
114
|
+
q.below = 0
|
115
|
+
end
|
116
|
+
assert_equal(-541, answer)
|
117
|
+
assert_equal( "Lowest numer you can think of?\n" +
|
118
|
+
"Your answer isn't within the expected range " +
|
119
|
+
"(below 0).\n" +
|
120
|
+
"? " +
|
121
|
+
"Your answer isn't within the expected range " +
|
122
|
+
"(below 0).\n" +
|
123
|
+
"? ", @output.string )
|
124
|
+
|
125
|
+
@input.truncate(@input.rewind)
|
126
|
+
@input << "1\n-541\n6\n"
|
127
|
+
@input.rewind
|
128
|
+
@output.truncate(@output.rewind)
|
129
|
+
|
130
|
+
answer = @terminal.ask("Enter a low even number: ", Integer) do |q|
|
131
|
+
q.above = 0
|
132
|
+
q.below = 10
|
133
|
+
q.in = [2, 4, 6, 8]
|
134
|
+
end
|
135
|
+
assert_equal(6, answer)
|
136
|
+
assert_equal( "Enter a low even number: " +
|
137
|
+
"Your answer isn't within the expected range " +
|
138
|
+
"(above 0, below 10, and included in [2, 4, 6, 8]).\n" +
|
139
|
+
"? " +
|
140
|
+
"Your answer isn't within the expected range " +
|
141
|
+
"(above 0, below 10, and included in [2, 4, 6, 8]).\n" +
|
142
|
+
"? ", @output.string )
|
143
|
+
end
|
144
|
+
|
145
|
+
def test_reask
|
146
|
+
number = 61676
|
147
|
+
@input << "Junk!\n" << number << "\n"
|
148
|
+
@input.rewind
|
149
|
+
|
150
|
+
answer = @terminal.ask("Favorite number? ", Integer)
|
151
|
+
assert_kind_of(Integer, number)
|
152
|
+
assert_instance_of(Fixnum, number)
|
153
|
+
assert_equal(number, answer)
|
154
|
+
assert_equal( "Favorite number? " +
|
155
|
+
"You must enter a valid Integer.\n" +
|
156
|
+
"? ", @output.string )
|
157
|
+
|
158
|
+
@input.rewind
|
159
|
+
@output.truncate(@output.rewind)
|
160
|
+
|
161
|
+
answer = @terminal.ask("Favorite number? ", Integer) do |q|
|
162
|
+
q.responses[:ask_on_error] = :question
|
163
|
+
q.responses[:invalid_type] = "Not a valid number!"
|
164
|
+
end
|
165
|
+
assert_kind_of(Integer, number)
|
166
|
+
assert_instance_of(Fixnum, number)
|
167
|
+
assert_equal(number, answer)
|
168
|
+
assert_equal( "Favorite number? " +
|
169
|
+
"Not a valid number!\n" +
|
170
|
+
"Favorite number? ", @output.string )
|
171
|
+
|
172
|
+
@input.truncate(@input.rewind)
|
173
|
+
@input << "gen\ngene\n"
|
174
|
+
@input.rewind
|
175
|
+
@output.truncate(@output.rewind)
|
176
|
+
|
177
|
+
answer = @terminal.ask("Select a mode: ", [:generate, :gentle])
|
178
|
+
assert_instance_of(Symbol, answer)
|
179
|
+
assert_equal(:generate, answer)
|
180
|
+
assert_equal("Select a mode: " +
|
181
|
+
"Ambiguous choice. " +
|
182
|
+
"Please choose one of [:generate, :gentle].\n" +
|
183
|
+
"? ", @output.string)
|
184
|
+
end
|
185
|
+
|
186
|
+
def test_say
|
187
|
+
@terminal.say("This will have a newline.")
|
188
|
+
assert_equal("This will have a newline.\n", @output.string)
|
189
|
+
|
190
|
+
@output.truncate(@output.rewind)
|
191
|
+
|
192
|
+
@terminal.say("This will also have one newline.\n")
|
193
|
+
assert_equal("This will also have one newline.\n", @output.string)
|
194
|
+
|
195
|
+
@output.truncate(@output.rewind)
|
196
|
+
|
197
|
+
@terminal.say("This will not have a newline. ")
|
198
|
+
assert_equal("This will not have a newline. ", @output.string)
|
199
|
+
end
|
200
|
+
|
201
|
+
def test_type_conversion
|
202
|
+
number = 61676
|
203
|
+
@input << number << "\n"
|
204
|
+
@input.rewind
|
205
|
+
|
206
|
+
answer = @terminal.ask("Favorite number? ", Integer)
|
207
|
+
assert_kind_of(Integer, answer)
|
208
|
+
assert_instance_of(Fixnum, answer)
|
209
|
+
assert_equal(number, answer)
|
210
|
+
|
211
|
+
@input.truncate(@input.rewind)
|
212
|
+
number = 1_000_000_000_000_000_000_000_000_000_000
|
213
|
+
@input << number << "\n"
|
214
|
+
@input.rewind
|
215
|
+
|
216
|
+
answer = @terminal.ask("Favorite number? ", Integer)
|
217
|
+
assert_kind_of(Integer, answer)
|
218
|
+
assert_instance_of(Bignum, answer)
|
219
|
+
assert_equal(number, answer)
|
220
|
+
|
221
|
+
@input.truncate(@input.rewind)
|
222
|
+
number = 10.5002
|
223
|
+
@input << number << "\n"
|
224
|
+
@input.rewind
|
225
|
+
|
226
|
+
answer = @terminal.ask( "Favorite number? ",
|
227
|
+
lambda { |n| n.to_f.abs.round } )
|
228
|
+
assert_kind_of(Integer, answer)
|
229
|
+
assert_instance_of(Fixnum, answer)
|
230
|
+
assert_equal(11, answer)
|
231
|
+
|
232
|
+
@input.truncate(@input.rewind)
|
233
|
+
animal = :dog
|
234
|
+
@input << animal << "\n"
|
235
|
+
@input.rewind
|
236
|
+
|
237
|
+
answer = @terminal.ask("Favorite animal? ", Symbol)
|
238
|
+
assert_instance_of(Symbol, answer)
|
239
|
+
assert_equal(animal, answer)
|
240
|
+
|
241
|
+
@input.truncate(@input.rewind)
|
242
|
+
@input << "6/16/76\n"
|
243
|
+
@input.rewind
|
244
|
+
|
245
|
+
answer = @terminal.ask("Enter your birthday.", Date)
|
246
|
+
assert_instance_of(Date, answer)
|
247
|
+
assert_equal(16, answer.day)
|
248
|
+
assert_equal(6, answer.month)
|
249
|
+
assert_equal(76, answer.year)
|
250
|
+
|
251
|
+
@input.truncate(@input.rewind)
|
252
|
+
pattern = "^yes|no$"
|
253
|
+
@input << pattern << "\n"
|
254
|
+
@input.rewind
|
255
|
+
|
256
|
+
answer = @terminal.ask("Give me a pattern to match with: ", Regexp)
|
257
|
+
assert_instance_of(Regexp, answer)
|
258
|
+
assert_equal(/#{pattern}/, answer)
|
259
|
+
|
260
|
+
@input.truncate(@input.rewind)
|
261
|
+
@input << "gen\n"
|
262
|
+
@input.rewind
|
263
|
+
|
264
|
+
answer = @terminal.ask("Select a mode: ", [:generate, :run])
|
265
|
+
assert_instance_of(Symbol, answer)
|
266
|
+
assert_equal(:generate, answer)
|
267
|
+
end
|
268
|
+
|
269
|
+
def test_validation
|
270
|
+
@input << "system 'rm -rf /'\n105\n0b101_001\n"
|
271
|
+
@input.rewind
|
272
|
+
|
273
|
+
answer = @terminal.ask("Enter a binary number: ") do |q|
|
274
|
+
q.validate = /\A(?:0b)?[01_]+\Z/
|
275
|
+
end
|
276
|
+
assert_equal("0b101_001", answer)
|
277
|
+
assert_equal( "Enter a binary number: " +
|
278
|
+
"Your answer isn't valid " +
|
279
|
+
"(must match /\\A(?:0b)?[01_]+\\Z/).\n" +
|
280
|
+
"? " +
|
281
|
+
"Your answer isn't valid " +
|
282
|
+
"(must match /\\A(?:0b)?[01_]+\\Z/).\n" +
|
283
|
+
"? ", @output.string )
|
284
|
+
|
285
|
+
@input.truncate(@input.rewind)
|
286
|
+
@input << "Gray II, James Edward\n" +
|
287
|
+
"Gray, Dana Ann Leslie\n" +
|
288
|
+
"Gray, James Edward\n"
|
289
|
+
@input.rewind
|
290
|
+
|
291
|
+
answer = @terminal.ask("Your name? ") do |q|
|
292
|
+
q.validate = lambda do |name|
|
293
|
+
names = name.split(/,\s*/)
|
294
|
+
return false unless names.size == 2
|
295
|
+
return false if names.first =~ /\s/
|
296
|
+
names.last.split.size == 2
|
297
|
+
end
|
298
|
+
end
|
299
|
+
assert_equal("Gray, James Edward", answer)
|
300
|
+
end
|
301
|
+
end
|
data/test/tc_import.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
#!/usr/local/bin/ruby -w
|
2
|
+
|
3
|
+
# tc_import.rb
|
4
|
+
#
|
5
|
+
# Created by James Edward Gray II on 2005-04-26.
|
6
|
+
# Copyright 2005 Gray Productions. All rights reserved.
|
7
|
+
|
8
|
+
$test_lib_dir ||= File.join(File.dirname(__FILE__), "..", "lib")
|
9
|
+
$:.unshift($test_lib_dir) unless $:.include?($test_lib_dir)
|
10
|
+
|
11
|
+
require "test/unit"
|
12
|
+
|
13
|
+
require "highline/import"
|
14
|
+
require "stringio"
|
15
|
+
|
16
|
+
class TestImport < Test::Unit::TestCase
|
17
|
+
def test_import
|
18
|
+
assert_respond_to(self, :agree)
|
19
|
+
assert_respond_to(self, :ask)
|
20
|
+
assert_respond_to(self, :say)
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_redirection
|
24
|
+
$terminal = HighLine.new(nil, (output = StringIO.new))
|
25
|
+
say("Testing...")
|
26
|
+
assert_equal("Testing...\n", output.string)
|
27
|
+
end
|
28
|
+
end
|
data/test/ts_all.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/local/bin/ruby -w
|
2
|
+
|
3
|
+
# ts_all.rb
|
4
|
+
#
|
5
|
+
# Created by James Edward Gray II on 2005-04-26.
|
6
|
+
# Copyright 2005 Gray Productions. All rights reserved.
|
7
|
+
|
8
|
+
$test_dir ||= File.dirname(__FILE__)
|
9
|
+
$:.unshift($test_dir) unless $:.include?($test_dir)
|
10
|
+
|
11
|
+
require "test/unit"
|
12
|
+
|
13
|
+
require "tc_highline"
|
14
|
+
require "tc_import"
|
metadata
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.8.10
|
3
|
+
specification_version: 1
|
4
|
+
name: highline
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 0.2.0
|
7
|
+
date: 2005-04-29
|
8
|
+
summary: HighLine is a high-level line oriented console interface.
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: james@grayproductions.net
|
12
|
+
homepage: http://highline.rubyforge.org
|
13
|
+
rubyforge_project: highline
|
14
|
+
description: "A \"high-level line oriented\" input/output library that grew out of my solution
|
15
|
+
to Ruby Quiz #29. This library attempts to make standard console input and
|
16
|
+
output robust and painless."
|
17
|
+
autorequire: highline
|
18
|
+
default_executable:
|
19
|
+
bindir: bin
|
20
|
+
has_rdoc: true
|
21
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
22
|
+
requirements:
|
23
|
+
-
|
24
|
+
- ">"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.0.0
|
27
|
+
version:
|
28
|
+
platform: ruby
|
29
|
+
authors:
|
30
|
+
- James Edward Gray II
|
31
|
+
files:
|
32
|
+
- examples/basic_usage.rb
|
33
|
+
- lib/highline.rb
|
34
|
+
- lib/highline/import.rb
|
35
|
+
- lib/highline/question.rb
|
36
|
+
- test/tc_highline.rb
|
37
|
+
- test/tc_import.rb
|
38
|
+
- test/ts_all.rb
|
39
|
+
- Rakefile
|
40
|
+
- README
|
41
|
+
- INSTALL
|
42
|
+
- TODO
|
43
|
+
- CHANGELOG
|
44
|
+
- LICENSE
|
45
|
+
test_files:
|
46
|
+
- test/ts_all.rb
|
47
|
+
rdoc_options:
|
48
|
+
- "--title"
|
49
|
+
- HighLine Documentation
|
50
|
+
- "--main"
|
51
|
+
- README
|
52
|
+
extra_rdoc_files:
|
53
|
+
- README
|
54
|
+
- INSTALL
|
55
|
+
- TODO
|
56
|
+
- CHANGELOG
|
57
|
+
- LICENSE
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
requirements: []
|
61
|
+
dependencies: []
|