highline 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +19 -1
- data/INSTALL +15 -2
- data/README +5 -5
- data/Rakefile +8 -2
- data/TODO +2 -7
- data/examples/ansi_colors.rb +32 -0
- data/examples/basic_usage.rb +22 -4
- data/lib/highline.rb +153 -22
- data/lib/highline/question.rb +95 -41
- data/setup.rb +1360 -0
- data/test/tc_highline.rb +141 -0
- metadata +4 -2
data/CHANGELOG
CHANGED
@@ -2,6 +2,22 @@
|
|
2
2
|
|
3
3
|
Below is a complete listing of changes for each revision of HighLine.
|
4
4
|
|
5
|
+
== 0.3.0
|
6
|
+
|
7
|
+
* Added support for installing with setup.rb.
|
8
|
+
* All output is now treated as an ERb sequence, allowing Ruby code to be
|
9
|
+
embedded in output strings.
|
10
|
+
* Added support for ANSI color sequences in say(). (And everything else
|
11
|
+
by extension.)
|
12
|
+
* Added whitespace handling for answers. Can be set to :strip, :chomp,
|
13
|
+
:collapse, :strip_and_collapse, :chomp_and_collapse, :remove, or :none
|
14
|
+
* Exposed question details to ERb completion through @question, to allow for
|
15
|
+
intelligent responses.
|
16
|
+
* Simplified HighLine internals using @question.
|
17
|
+
* Added support for fetching single character input either with getc() or
|
18
|
+
HighLine's own cross-platform terminal input routine.
|
19
|
+
* Improved type conversion to handle user defined classes.
|
20
|
+
|
5
21
|
== 0.2.0
|
6
22
|
|
7
23
|
* Added Unit Tests to cover an already fixed output bug in the future.
|
@@ -17,12 +33,14 @@ Below is a complete listing of changes for each revision of HighLine.
|
|
17
33
|
* Fixed the bug causing ask() to swallow NoMethodErrors.
|
18
34
|
* Rolled ask_on_error() into responses.
|
19
35
|
* Redirected imports to Kernel from Object.
|
20
|
-
* Added support for <tt>validate lambda { ... }</tt>.
|
36
|
+
* Added support for <tt>validate = lambda { ... }</tt>.
|
21
37
|
* Added default answer support.
|
22
38
|
* Fixed bug that caused ask() to die with an empty question.
|
23
39
|
* Added complete documentation.
|
24
40
|
* Improve the implemetation of agree() to be the intended "yes" or "no" only
|
25
41
|
question.
|
42
|
+
* Added Rake tasks for documentation and packaging.
|
43
|
+
* Moved project to RubyForge.
|
26
44
|
|
27
45
|
== 0.1.0
|
28
46
|
|
data/INSTALL
CHANGED
@@ -1,10 +1,23 @@
|
|
1
|
-
= Installing
|
1
|
+
= Installing HighLine
|
2
|
+
|
3
|
+
RubyGems is the preferred easy install method for HighLine. However, you can
|
4
|
+
install HighLine manually as described below.
|
5
|
+
|
6
|
+
== Installing the Gem
|
2
7
|
|
3
8
|
HighLine is intended to be installed via the
|
4
9
|
RubyGems[http://rubyforge.org/projects/rubygems/] system. To get the latest
|
5
10
|
version, simply enter the following into your command prompt:
|
6
11
|
|
7
|
-
sudo gem install highline
|
12
|
+
$ sudo gem install highline
|
8
13
|
|
9
14
|
You must have RubyGems[http://rubyforge.org/projects/rubygems/] installed for
|
10
15
|
the above to work.
|
16
|
+
|
17
|
+
== Installing Manually
|
18
|
+
|
19
|
+
Download the latest version of HighLine from the
|
20
|
+
{RubyForge project page}[http://rubyforge.org/frs/?group_id=683]. Navigate to
|
21
|
+
the root project directory and enter:
|
22
|
+
|
23
|
+
$ sudo ruby setup.rb
|
data/README
CHANGED
@@ -6,12 +6,12 @@ by James Edward Gray II
|
|
6
6
|
|
7
7
|
Welcome to HighLine.
|
8
8
|
|
9
|
-
|
10
|
-
output with low-level methods like gets() and puts().
|
11
|
-
robust system
|
9
|
+
HighLine was designed to ease the tedious tasks of doing console input and
|
10
|
+
output with low-level methods like gets() and puts(). HighLine provides a
|
11
|
+
robust system for requesting data from a user, without needing to code all the
|
12
12
|
error checking and validation rules and without needing to convert the typed
|
13
13
|
Strings into what your program really needs. Just tell HighLine what you're
|
14
|
-
after, and
|
14
|
+
after, and let it do all the work.
|
15
15
|
|
16
16
|
== Documentation
|
17
17
|
|
@@ -25,7 +25,7 @@ See the examples/ directory of this project for code examples.
|
|
25
25
|
|
26
26
|
See the INSTALL file for instructions.
|
27
27
|
|
28
|
-
== Questions
|
28
|
+
== Questions and/or Comments
|
29
29
|
|
30
30
|
Feel free to email {James Edward Gray II}[mailto:james@grayproductions.net] with
|
31
31
|
any questions.
|
data/Rakefile
CHANGED
@@ -21,13 +21,19 @@ Rake::RDocTask.new do |rdoc|
|
|
21
21
|
rdoc.title = "HighLine Documentation"
|
22
22
|
end
|
23
23
|
|
24
|
+
task :upload_docs => [:rdoc] do
|
25
|
+
sh "scp -r doc/html/* " +
|
26
|
+
"bbazzarrakk@rubyforge.org:/var/www/gforge-projects/highline/"
|
27
|
+
end
|
28
|
+
|
24
29
|
spec = Gem::Specification.new do |spec|
|
25
30
|
spec.name = "highline"
|
26
|
-
spec.version = "0.
|
31
|
+
spec.version = "0.3.0"
|
27
32
|
spec.platform = Gem::Platform::RUBY
|
28
33
|
spec.summary = "HighLine is a high-level line oriented console interface."
|
29
34
|
spec.files = Dir.glob("{examples,lib,test}/**/*.rb").
|
30
|
-
delete_if { |item| item.include?("CVS") } +
|
35
|
+
delete_if { |item| item.include?("CVS") } +
|
36
|
+
["Rakefile", "setup.rb"]
|
31
37
|
spec.test_suite_file = "test/ts_all.rb"
|
32
38
|
spec.has_rdoc = true
|
33
39
|
spec.extra_rdoc_files = %w{README INSTALL TODO CHANGELOG LICENSE}
|
data/TODO
CHANGED
@@ -3,14 +3,9 @@
|
|
3
3
|
The following is a list of planned expansions for HighLine, in no particular
|
4
4
|
order.
|
5
5
|
|
6
|
-
* Expose response replacements for user code strings.
|
7
|
-
* Support character oriented input.
|
8
6
|
* Support <tt>ask(..., Array)</tt>, better than the current
|
9
7
|
<tt>ask(..., lambda { |arr| arr.split(",") })</tt> or similar.
|
10
8
|
* Support <tt>ask(..., Hash)</tt>.
|
11
|
-
* Provide for <tt>ask(..., MyClass)</tt>.
|
12
|
-
* Add ANSI color support to say().
|
13
9
|
* Add word wrap support to say().
|
14
|
-
*
|
15
|
-
|
16
|
-
* Support install with setup.rb.
|
10
|
+
* Implement choose() for simple menu handling.
|
11
|
+
* Provide a +no_echo+ option, for password requests.
|
@@ -0,0 +1,32 @@
|
|
1
|
+
#!/usr/local/bin/ruby -w
|
2
|
+
|
3
|
+
# ansi_colors.rb
|
4
|
+
#
|
5
|
+
# Created by James Edward Gray II on 2005-05-03.
|
6
|
+
# Copyright 2005 Gray Productions. All rights reserved.
|
7
|
+
|
8
|
+
require "rubygems"
|
9
|
+
require "highline/import"
|
10
|
+
|
11
|
+
# Supported color sequences.
|
12
|
+
colors = %w{black red green yellow blue magenta cyan white}
|
13
|
+
|
14
|
+
# Using color() with symbols.
|
15
|
+
colors.each_with_index do |c, i|
|
16
|
+
say("This should be <%= color('#{c}', :#{c}) %>!")
|
17
|
+
if i == 0
|
18
|
+
say( "This should be " +
|
19
|
+
"<%= color('white on #{c}', :white, :on_#{c}) %>!")
|
20
|
+
else
|
21
|
+
say( "This should be " +
|
22
|
+
"<%= color( '#{colors[i - 1]} on #{c}',
|
23
|
+
:#{colors[i - 1]}, :on_#{c} ) %>!")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Using color with constants.
|
28
|
+
say("This should be <%= color('bold', BOLD) %>!")
|
29
|
+
say("This should be <%= color('underlined', UNDERLINE) %>!")
|
30
|
+
|
31
|
+
# Using constants only.
|
32
|
+
say("This might even <%= BLINK %>blink<%= CLEAR %>!")
|
data/examples/basic_usage.rb
CHANGED
@@ -11,6 +11,22 @@ require "yaml"
|
|
11
11
|
|
12
12
|
contacts = [ ]
|
13
13
|
|
14
|
+
class NameClass
|
15
|
+
def self.parse( string )
|
16
|
+
if string =~ /^\s*(\w+),\s*(\w+)\s*$/
|
17
|
+
self.new($2, $1)
|
18
|
+
else
|
19
|
+
raise ArgumentError, "Invalid name format."
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(first, last)
|
24
|
+
@first, @last = first, last
|
25
|
+
end
|
26
|
+
|
27
|
+
attr_reader :first, :last
|
28
|
+
end
|
29
|
+
|
14
30
|
begin
|
15
31
|
entry = Hash.new
|
16
32
|
|
@@ -18,7 +34,7 @@ begin
|
|
18
34
|
say("Enter a contact:")
|
19
35
|
|
20
36
|
# basic input
|
21
|
-
entry[:name] = ask("Name? (last, first) ") do |q|
|
37
|
+
entry[:name] = ask("Name? (last, first) ", NameClass) do |q|
|
22
38
|
q.validate = /\A\w+, ?\w+\Z/
|
23
39
|
end
|
24
40
|
entry[:company] = ask("Company? ") { |q| q.default = "none" }
|
@@ -39,13 +55,15 @@ begin
|
|
39
55
|
entry[:birthday] = ask("Birthday? ", Date)
|
40
56
|
entry[:interests] = ask( "Interests? (comma separated list) ",
|
41
57
|
lambda { |str| str.split(/,\s*/) } )
|
42
|
-
entry[:description] = ask("Enter a description for this contact.")
|
58
|
+
entry[:description] = ask("Enter a description for this contact.") do |q|
|
59
|
+
q.whitespace = :strip_and_collapse
|
60
|
+
end
|
43
61
|
|
44
62
|
contacts << entry
|
45
63
|
# shortcut for yes and no questions
|
46
|
-
end while agree("Enter another contact? ")
|
64
|
+
end while agree("Enter another contact? ", true)
|
47
65
|
|
48
|
-
if agree("Save these contacts? ")
|
66
|
+
if agree("Save these contacts? ", true)
|
49
67
|
file_name = ask("Enter a file name: ") { |q| q.validate = /\A\w+\Z/ }
|
50
68
|
File.open("#{file_name}.yaml", "w") { |file| YAML.dump(contacts, file) }
|
51
69
|
end
|
data/lib/highline.rb
CHANGED
@@ -8,6 +8,7 @@
|
|
8
8
|
# See HighLine for documentation.
|
9
9
|
|
10
10
|
require "highline/question"
|
11
|
+
require "erb"
|
11
12
|
|
12
13
|
#
|
13
14
|
# A HighLine object is a "high-level line oriented" shell over an input and an
|
@@ -23,9 +24,67 @@ class HighLine
|
|
23
24
|
class QuestionError < StandardError
|
24
25
|
# do nothing, just creating a unique error type
|
25
26
|
end
|
26
|
-
|
27
|
+
|
28
|
+
#
|
29
|
+
# Embed in a String to clear all previous ANSI sequences. This *MUST* be
|
30
|
+
# done before the program exits!
|
31
|
+
#
|
32
|
+
CLEAR = "\e[0m"
|
33
|
+
# An alias for CLEAR.
|
34
|
+
RESET = CLEAR
|
35
|
+
# The start of an ANSI bold sequence.
|
36
|
+
BOLD = "\e[1m"
|
37
|
+
# The start of an ANSI dark sequence. (Terminal support uncommon.)
|
38
|
+
DARK = "\e[2m"
|
39
|
+
# The start of an ANSI underline sequence.
|
40
|
+
UNDERLINE = "\e[4m"
|
41
|
+
# An alias for UNDERLINE.
|
42
|
+
UNDERSCORE = UNDERLINE
|
43
|
+
# The start of an ANSI blink sequence. (Terminal support uncommon.)
|
44
|
+
BLINK = "\e[5m"
|
45
|
+
# The start of an ANSI reverse sequence.
|
46
|
+
REVERSE = "\e[7m"
|
47
|
+
# The start of an ANSI concealed sequence. (Terminal support uncommon.)
|
48
|
+
CONCEALED = "\e[8m"
|
49
|
+
|
50
|
+
# Set the terminal's foreground ANSI color to black.
|
51
|
+
BLACK = "\e[30m"
|
52
|
+
# Set the terminal's foreground ANSI color to red.
|
53
|
+
RED = "\e[31m"
|
54
|
+
# Set the terminal's foreground ANSI color to green.
|
55
|
+
GREEN = "\e[32m"
|
56
|
+
# Set the terminal's foreground ANSI color to yellow.
|
57
|
+
YELLOW = "\e[33m"
|
58
|
+
# Set the terminal's foreground ANSI color to blue.
|
59
|
+
BLUE = "\e[34m"
|
60
|
+
# Set the terminal's foreground ANSI color to magenta.
|
61
|
+
MAGENTA = "\e[35m"
|
62
|
+
# Set the terminal's foreground ANSI color to cyan.
|
63
|
+
CYAN = "\e[36m"
|
64
|
+
# Set the terminal's foreground ANSI color to white.
|
65
|
+
WHITE = "\e[37m"
|
66
|
+
|
67
|
+
# Set the terminal's background ANSI color to black.
|
68
|
+
ON_BLACK = "\e[40m"
|
69
|
+
# Set the terminal's background ANSI color to red.
|
70
|
+
ON_RED = "\e[41m"
|
71
|
+
# Set the terminal's background ANSI color to green.
|
72
|
+
ON_GREEN = "\e[42m"
|
73
|
+
# Set the terminal's background ANSI color to yellow.
|
74
|
+
ON_YELLOW = "\e[43m"
|
75
|
+
# Set the terminal's background ANSI color to blue.
|
76
|
+
ON_BLUE = "\e[44m"
|
77
|
+
# Set the terminal's background ANSI color to magenta.
|
78
|
+
ON_MAGENTA = "\e[45m"
|
79
|
+
# Set the terminal's background ANSI color to cyan.
|
80
|
+
ON_CYAN = "\e[46m"
|
81
|
+
# Set the terminal's background ANSI color to white.
|
82
|
+
ON_WHITE = "\e[47m"
|
83
|
+
|
84
|
+
#
|
27
85
|
# Create an instance of HighLine, connected to the streams _input_
|
28
86
|
# and _output_.
|
87
|
+
#
|
29
88
|
def initialize( input = $stdin, output = $stdout )
|
30
89
|
@input = input
|
31
90
|
@output = output
|
@@ -34,13 +93,15 @@ class HighLine
|
|
34
93
|
#
|
35
94
|
# A shortcut to HighLine.ask() a question that only accepts "yes" or "no"
|
36
95
|
# answers ("y" and "n" are allowed) and returns +true+ or +false+
|
37
|
-
# (+true+ for "yes").
|
96
|
+
# (+true+ for "yes"). If provided a +true+ value, _character_ will cause
|
97
|
+
# HighLine to fetch a single character response.
|
38
98
|
#
|
39
|
-
def agree( yes_or_no_question )
|
99
|
+
def agree( yes_or_no_question, character = nil )
|
40
100
|
ask(yes_or_no_question, lambda { |yn| yn.downcase[0] == ?y}) do |q|
|
41
101
|
q.validate = /\Ay(?:es)?|no?\Z/i
|
42
102
|
q.responses[:not_valid] = 'Please enter "yes" or "no".'
|
43
103
|
q.responses[:ask_on_error] = :question
|
104
|
+
q.character = character
|
44
105
|
end
|
45
106
|
end
|
46
107
|
|
@@ -53,45 +114,72 @@ class HighLine
|
|
53
114
|
# valid in the code block.
|
54
115
|
#
|
55
116
|
def ask( question, answer_type = String, &details ) # :yields: question
|
56
|
-
question = Question.new(question, answer_type, &details)
|
117
|
+
@question = Question.new(question, answer_type, &details)
|
57
118
|
|
58
|
-
say(question)
|
119
|
+
say(@question)
|
59
120
|
begin
|
60
|
-
answer = question.answer_or_default(get_response)
|
61
|
-
unless question.valid_answer?(answer)
|
62
|
-
explain_error(
|
121
|
+
answer = @question.answer_or_default(get_response )
|
122
|
+
unless @question.valid_answer?(answer)
|
123
|
+
explain_error(:not_valid)
|
63
124
|
raise QuestionError
|
64
125
|
end
|
65
126
|
|
66
|
-
answer = question.convert(answer)
|
127
|
+
answer = @question.convert(answer)
|
67
128
|
|
68
|
-
if question.in_range?(answer)
|
129
|
+
if @question.in_range?(answer)
|
69
130
|
answer
|
70
131
|
else
|
71
|
-
explain_error(
|
132
|
+
explain_error(:not_in_range)
|
72
133
|
raise QuestionError
|
73
134
|
end
|
74
135
|
rescue QuestionError
|
75
136
|
retry
|
76
137
|
rescue ArgumentError
|
77
|
-
explain_error(
|
138
|
+
explain_error(:invalid_type)
|
78
139
|
retry
|
79
140
|
rescue NameError
|
80
141
|
raise if $!.is_a?(NoMethodError)
|
81
|
-
explain_error(
|
142
|
+
explain_error(:ambiguous_completion)
|
82
143
|
retry
|
83
144
|
end
|
84
145
|
end
|
146
|
+
|
147
|
+
#
|
148
|
+
# This method provides easy access to ANSI color sequences, without the user
|
149
|
+
# needing to remember to CLEAR at the end of each sequence. Just pass the
|
150
|
+
# _string_ to color, followed by a list of _colors_ you would like it to be
|
151
|
+
# affected by. The _colors_ can be HighLine class constants, or symbols
|
152
|
+
# (:blue for BLUE, for example). A CLEAR will automatically be embedded to
|
153
|
+
# the end of the returned String.
|
154
|
+
#
|
155
|
+
def color( string, *colors )
|
156
|
+
colors.map! do |c|
|
157
|
+
if c.is_a?(Symbol)
|
158
|
+
self.class.const_get(c.to_s.upcase)
|
159
|
+
else
|
160
|
+
c
|
161
|
+
end
|
162
|
+
end
|
163
|
+
"#{colors.join}#{string}#{CLEAR}"
|
164
|
+
end
|
85
165
|
|
86
166
|
#
|
87
167
|
# The basic output method for HighLine objects. If the provided _statement_
|
88
168
|
# ends with a space or tab character, a newline will not be appended (output
|
89
169
|
# will be flush()ed). All other cases are passed straight to Kernel.puts().
|
90
170
|
#
|
171
|
+
# The _statement_ parameter is processed as an ERb template, supporting
|
172
|
+
# embedded Ruby code. The template is evaluated with a binding inside
|
173
|
+
# the HighLine instance, providing easy access to the ANSI color constants
|
174
|
+
# and the HighLine.color() method.
|
175
|
+
#
|
91
176
|
def say( statement )
|
92
177
|
statement = statement.to_s
|
93
178
|
return unless statement.length > 0
|
94
179
|
|
180
|
+
template = ERB.new(statement, nil, "%")
|
181
|
+
statement = template.result(binding)
|
182
|
+
|
95
183
|
if statement[-1, 1] == " " or statement[-1, 1] == "\t"
|
96
184
|
@output.print(statement)
|
97
185
|
@output.flush
|
@@ -106,20 +194,63 @@ class HighLine
|
|
106
194
|
# A helper method for sending the output stream and error and repeat
|
107
195
|
# of the question.
|
108
196
|
#
|
109
|
-
def explain_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])
|
197
|
+
def explain_error( error )
|
198
|
+
say(@question.responses[error])
|
199
|
+
if @question.responses[:ask_on_error] == :question
|
200
|
+
say(@question)
|
201
|
+
elsif @question.responses[:ask_on_error]
|
202
|
+
say(@question.responses[:ask_on_error])
|
115
203
|
end
|
116
204
|
end
|
117
205
|
|
206
|
+
begin
|
207
|
+
require "Win32API"
|
208
|
+
|
209
|
+
#
|
210
|
+
# Windows savvy getc().
|
211
|
+
#
|
212
|
+
# WARNING: This method ignores @input and reads one character
|
213
|
+
# from STDIN!
|
214
|
+
#
|
215
|
+
def get_character
|
216
|
+
Win32API.new("crtdll", "_getch", [], "L").Call
|
217
|
+
end
|
218
|
+
rescue LoadError
|
219
|
+
#
|
220
|
+
# Unix savvy getc().
|
221
|
+
#
|
222
|
+
# WARNING: This method requires the external "stty" program!
|
223
|
+
#
|
224
|
+
def get_character
|
225
|
+
system "stty raw -echo"
|
226
|
+
@input.getc
|
227
|
+
ensure
|
228
|
+
system "stty -raw echo"
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
#
|
233
|
+
# Read a line of input from the input stream and process whitespace as
|
234
|
+
# requested by the Question object.
|
235
|
+
#
|
236
|
+
def get_line( )
|
237
|
+
@question.remove_whitespace(@input.gets)
|
238
|
+
end
|
239
|
+
|
118
240
|
#
|
119
|
-
#
|
120
|
-
#
|
241
|
+
# Return a line or character of input, as requested for this question.
|
242
|
+
# Character input will be returned as a single character String,
|
243
|
+
# not an Integer.
|
121
244
|
#
|
122
245
|
def get_response( )
|
123
|
-
@
|
246
|
+
if @question.character.nil?
|
247
|
+
get_line
|
248
|
+
elsif @question.character == :getc
|
249
|
+
@input.getc.chr
|
250
|
+
else
|
251
|
+
response = get_character.chr
|
252
|
+
say("#{response}\n")
|
253
|
+
response
|
254
|
+
end
|
124
255
|
end
|
125
256
|
end
|