highline 0.2.0 → 0.3.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 +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
|