highline 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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: []
|