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 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 the Gem
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
- 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
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 leg it do all the leg work.
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.2.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") } + ["Rakefile"]
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
- * Allow user code to specify whitespace handling for <tt>@input</tt>
15
- (chomp(), strip(), etc.).
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 %>!")
@@ -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
@@ -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(question, :not_valid)
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(question, :not_in_range)
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(question, :invalid_type)
138
+ explain_error(:invalid_type)
78
139
  retry
79
140
  rescue NameError
80
141
  raise if $!.is_a?(NoMethodError)
81
- explain_error(question, :ambiguous_completion)
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( 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])
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
- # Read a line of input from the input stream, chomp()ing any newline
120
- # characters.
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
- @input.gets.chomp
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