highline 0.4.0 → 0.5.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.5.0
6
+
7
+ * Implemented <tt>echo = false</tt> for HighLine::Question objects, primarily to
8
+ make fetching passwords trivial.
9
+ * Fixed an auto-complete bug that could cause a crash when the user gave an
10
+ answer that didn't complete to any valid choice.
11
+ * Implemented +case+ for HighLine::Question objects to provide character case
12
+ conversions on given answers. Can be set to <tt>:up</tt>, <tt>:down</tt>, or
13
+ <tt>:capitalize</tt>.
14
+ * Exposed <tt>@answer</tt> to the response system, to allow response that are
15
+ aware of incorrect input.
16
+ * Implemented +confirm+ for HighLine::Question objects to allow for verification
17
+ for sensitive user choices. If set to +true+, user will have to answer an
18
+ "Are you sure? " question. Can also be set to the question to confirm with
19
+ the user.
20
+
5
21
  == 0.4.0
6
22
 
7
23
  * Added <tt>@wrap_at</tt> and <tt>@page_at</tt> settings and accessors to
data/Rakefile CHANGED
@@ -28,7 +28,7 @@ end
28
28
 
29
29
  spec = Gem::Specification.new do |spec|
30
30
  spec.name = "highline"
31
- spec.version = "0.4.0"
31
+ spec.version = "0.5.0"
32
32
  spec.platform = Gem::Platform::RUBY
33
33
  spec.summary = "HighLine is a high-level line oriented console interface."
34
34
  spec.files = Dir.glob("{examples,lib,test}/**/*.rb").
data/TODO CHANGED
@@ -6,6 +6,6 @@ order.
6
6
  * Support <tt>ask(..., Array)</tt>, better than the current
7
7
  <tt>ask(..., lambda { |arr| arr.split(",") })</tt> or similar.
8
8
  * Support <tt>ask(..., Hash)</tt>.
9
+ * Support <tt>ask(..., File)</tt>.
9
10
  * Implement choose() for simple menu handling.
10
- * Provide a +no_echo+ option, for password requests.
11
- * Add an "Are you sure?" check for ask().
11
+ * Add readline support for history and editing.
@@ -40,7 +40,10 @@ begin
40
40
  entry[:company] = ask("Company? ") { |q| q.default = "none" }
41
41
  entry[:address] = ask("Address? ")
42
42
  entry[:city] = ask("City? ")
43
- entry[:state] = ask("State? ") { |q| q.validate = /\A[A-Z]{2}\Z/ }
43
+ entry[:state] = ask("State? ") do |q|
44
+ q.case = :up
45
+ q.validate = /\A[A-Z]{2}\Z/
46
+ end
44
47
  entry[:zip] = ask("Zip? ") do |q|
45
48
  q.validate = /\A\d{5}(?:-?\d{4})?\Z/
46
49
  end
@@ -64,6 +67,9 @@ begin
64
67
  end while agree("Enter another contact? ", true)
65
68
 
66
69
  if agree("Save these contacts? ", true)
67
- file_name = ask("Enter a file name: ") { |q| q.validate = /\A\w+\Z/ }
70
+ file_name = ask("Enter a file name: ") do |q|
71
+ q.validate = /\A\w+\Z/
72
+ q.confirm = true
73
+ end
68
74
  File.open("#{file_name}.yaml", "w") { |file| YAML.dump(contacts, file) }
69
75
  end
@@ -0,0 +1,16 @@
1
+ #!/usr/local/bin/ruby -w
2
+
3
+ require "rubygems"
4
+ require "highline/import"
5
+
6
+ choices = %w{ruby python perl}
7
+
8
+ say("Please choose your favorite programming language:")
9
+ say(choices.map { |c| " #{c}\n" }.join)
10
+
11
+ case ask("? ", choices)
12
+ when "ruby"
13
+ say("Good choice!")
14
+ else
15
+ say("Not from around here, are you?")
16
+ end
@@ -0,0 +1,7 @@
1
+ #!/usr/local/bin/ruby -w
2
+
3
+ require "rubygems"
4
+ require "highline/import"
5
+
6
+ pass = ask("Enter your password: ") { |q| q.echo = false }
7
+ puts "Your password is #{pass}!"
@@ -132,16 +132,35 @@ class HighLine
132
132
 
133
133
  say(@question)
134
134
  begin
135
- answer = @question.answer_or_default(get_response )
136
- unless @question.valid_answer?(answer)
135
+ @answer = @question.answer_or_default(get_response)
136
+ unless @question.valid_answer?(@answer)
137
137
  explain_error(:not_valid)
138
138
  raise QuestionError
139
139
  end
140
140
 
141
- answer = @question.convert(answer)
141
+ @answer = @question.convert(@answer)
142
142
 
143
- if @question.in_range?(answer)
144
- answer
143
+ if @question.in_range?(@answer)
144
+ if @question.confirm
145
+ # need to add a layer of scope to ask a question inside a
146
+ # question, without destroying instance data
147
+ context_change = self.class.new( @input, @output,
148
+ @wrap_at, @page_at )
149
+ if @question.confirm == true
150
+ confirm_question = "Are you sure? "
151
+ else
152
+ # evaluate ERb under initial scope, so it will have
153
+ # access to @question and @answer
154
+ template = ERB.new(@question.confirm, nil, "%")
155
+ confirm_question = template.result(binding)
156
+ end
157
+ unless context_change.agree(confirm_question)
158
+ explain_error(nil)
159
+ raise QuestionError
160
+ end
161
+ end
162
+
163
+ @answer
145
164
  else
146
165
  explain_error(:not_in_range)
147
166
  raise QuestionError
@@ -212,7 +231,7 @@ class HighLine
212
231
  # of the question.
213
232
  #
214
233
  def explain_error( error )
215
- say(@question.responses[error])
234
+ say(@question.responses[error]) unless error.nil?
216
235
  if @question.responses[:ask_on_error] == :question
217
236
  say(@question)
218
237
  elsif @question.responses[:ask_on_error]
@@ -251,7 +270,7 @@ class HighLine
251
270
  # requested by the Question object.
252
271
  #
253
272
  def get_line( )
254
- @question.remove_whitespace(@input.gets)
273
+ @question.change_case(@question.remove_whitespace(@input.gets))
255
274
  end
256
275
 
257
276
  #
@@ -261,13 +280,24 @@ class HighLine
261
280
  #
262
281
  def get_response( )
263
282
  if @question.character.nil?
264
- get_line
283
+ if @question.echo
284
+ get_line
285
+ else
286
+ line = ""
287
+ while character = get_character
288
+ line << character.chr
289
+
290
+ break if character == 13
291
+ end
292
+ say("\n")
293
+ @question.change_case(@question.remove_whitespace(line))
294
+ end
265
295
  elsif @question.character == :getc
266
- @input.getc.chr
296
+ @question.change_case(@input.getc.chr)
267
297
  else
268
298
  response = get_character.chr
269
- say("#{response}\n")
270
- response
299
+ say("#{if @question.echo then response else '' end}\n")
300
+ @question.change_case(response)
271
301
  end
272
302
  end
273
303
 
@@ -29,12 +29,15 @@ class HighLine
29
29
  @answer_type = answer_type
30
30
 
31
31
  @character = nil
32
+ @echo = true
32
33
  @whitespace = :strip
34
+ @case = nil
33
35
  @default = nil
34
36
  @validate = nil
35
37
  @above = nil
36
38
  @below = nil
37
39
  @in = nil
40
+ @confirm = nil
38
41
  @responses = Hash.new
39
42
 
40
43
  # allow block to override settings
@@ -67,10 +70,23 @@ class HighLine
67
70
  #
68
71
  attr_accessor :character
69
72
  #
73
+ # Can be set to +true+ or +false+ to control whether or not input will
74
+ # be echoed back to the user.
75
+ #
76
+ # This requires HighLine's character reader. See the _character_
77
+ # attribute for details.
78
+ #
79
+ attr_accessor :echo
80
+ #
70
81
  # Used to control whitespace processing for the answer to this question.
71
82
  # See HighLine::Question.remove_whitespace() for acceptable settings.
72
83
  #
73
84
  attr_accessor :whitespace
85
+ #
86
+ # Used to control whitespace processing for the answer to this question.
87
+ # See HighLine::Question.change_case() for acceptable settings.
88
+ #
89
+ attr_accessor :case
74
90
  # Used to provide a default answer to this question.
75
91
  attr_accessor :default
76
92
  #
@@ -84,6 +100,14 @@ class HighLine
84
100
  # If set, answer must pass an include?() check on this object.
85
101
  attr_accessor :in
86
102
  #
103
+ # Asks a yes or no confirmation question, to ensure a user knows what
104
+ # they have just agreed to. If set to +true+ the question will be,
105
+ # "Are you sure? " Any other true value for this attribute is assumed
106
+ # to be the question to ask. When +false+ or +nil+ (the default),
107
+ # answers are not confirmed.
108
+ #
109
+ attr_accessor :confirm
110
+ #
87
111
  # A Hash that stores the various responses used by HighLine to notify
88
112
  # the user. The currently used responses and their purpose are as
89
113
  # follows:
@@ -117,6 +141,32 @@ class HighLine
117
141
  answer_string
118
142
  end
119
143
  end
144
+
145
+ #
146
+ # Returns the provided _answer_string_ after changing character case by
147
+ # the rules of this Question. Valid settings for whitespace are:
148
+ #
149
+ # +nil+:: Do not alter character case.
150
+ # (Default.)
151
+ # <tt>:up</tt>:: Calls upcase().
152
+ # <tt>:upcase</tt>:: Calls upcase().
153
+ # <tt>:down</tt>:: Calls downcase().
154
+ # <tt>:downcase</tt>:: Calls downcase().
155
+ # <tt>:capitalize</tt>:: Calls capitalize().
156
+ #
157
+ # An unrecognized choice (like <tt>:none</tt>) is treated as +nil+.
158
+ #
159
+ def change_case( answer_string )
160
+ if [:up, :upcase].include?(@case)
161
+ answer_string.upcase
162
+ elsif [:down, :downcase].include?(@case)
163
+ answer_string.downcase
164
+ elsif @case == :capitalize
165
+ answer_string.capitalize
166
+ else
167
+ answer_string
168
+ end
169
+ end
120
170
 
121
171
  #
122
172
  # Transforms the given _answer_string_ into the expected type for this
@@ -153,7 +203,11 @@ class HighLine
153
203
  elsif @answer_type.is_a?(Array)
154
204
  # cheating, using OptionParser's Completion module
155
205
  @answer_type.extend(OptionParser::Completion)
156
- @answer_type.complete(answer_string).last
206
+ answer = @answer_type.complete(answer_string)
207
+ if answer.nil?
208
+ raise ArgumentError, "Not matching auto-complete choice."
209
+ end
210
+ answer.last
157
211
  elsif [Date, DateTime].include?(@answer_type) or
158
212
  @answer_type.is_a?(Class)
159
213
  @answer_type.parse(answer_string)
@@ -207,6 +261,8 @@ class HighLine
207
261
  #
208
262
  # An unrecognized choice (like <tt>:none</tt>) is treated as +nil+.
209
263
  #
264
+ # This process is skipped, for single character input.
265
+ #
210
266
  def remove_whitespace( answer_string )
211
267
  if @whitespace.nil?
212
268
  answer_string
@@ -233,6 +289,9 @@ class HighLine
233
289
  #
234
290
  # Returns +true+ if the provided _answer_string_ is accepted by the
235
291
  # _validate_ attribute or +false+ if it's not.
292
+ #
293
+ # It's important to realize that an answer is validated after whitespace
294
+ # and case handling.
236
295
  #
237
296
  def valid_answer?( answer_string )
238
297
  @validate.nil? or
@@ -44,6 +44,46 @@ class TestHighLine < Test::Unit::TestCase
44
44
  assert_equal(name, @terminal.ask("What is your name? "))
45
45
  end
46
46
 
47
+ def test_bug_fixes
48
+ # auto-complete bug
49
+ @input << "ruby\nRuby\n"
50
+ @input.rewind
51
+
52
+ languages = [:Perl, :Python, :Ruby]
53
+ answer = @terminal.ask( "What is your favorite programming language? ",
54
+ languages )
55
+ assert_equal(languages.last, answer)
56
+
57
+ @input.truncate(@input.rewind)
58
+ @input << "ruby\n"
59
+ @input.rewind
60
+
61
+ answer = @terminal.ask( "What is your favorite programming language? ",
62
+ languages ) do |q|
63
+ q.case = :capitalize
64
+ end
65
+ assert_equal(languages.last, answer)
66
+ end
67
+
68
+ def test_case_changes
69
+ @input << "jeg2\n"
70
+ @input.rewind
71
+
72
+ answer = @terminal.ask("Enter your initials ") do |q|
73
+ q.case = :up
74
+ end
75
+ assert_equal("JEG2", answer)
76
+
77
+ @input.truncate(@input.rewind)
78
+ @input << "cRaZY\n"
79
+ @input.rewind
80
+
81
+ answer = @terminal.ask("Enter a search string: ") do |q|
82
+ q.case = :down
83
+ end
84
+ assert_equal("crazy", answer)
85
+ end
86
+
47
87
  def test_character_reading
48
88
  # WARNING: This method does NOT cover Unix and Windows savvy testing!
49
89
  @input << "12345"
@@ -79,6 +119,35 @@ class TestHighLine < Test::Unit::TestCase
79
119
  @output.string )
80
120
  end
81
121
 
122
+ def test_confirm
123
+ @input << "junk.txt\nno\nsave.txt\ny\n"
124
+ @input.rewind
125
+
126
+ answer = @terminal.ask("Enter a filename: ") do |q|
127
+ q.confirm = "Are you sure you want to overwrite <%= @answer %>? "
128
+ q.responses[:ask_on_error] = :question
129
+ end
130
+ assert_equal("save.txt", answer)
131
+ assert_equal( "Enter a filename: " +
132
+ "Are you sure you want to overwrite junk.txt? " +
133
+ "Enter a filename: " +
134
+ "Are you sure you want to overwrite save.txt? ",
135
+ @output.string )
136
+
137
+ @input.truncate(@input.rewind)
138
+ @input << "junk.txt\nyes\nsave.txt\nn\n"
139
+ @input.rewind
140
+ @output.truncate(@output.rewind)
141
+
142
+ answer = @terminal.ask("Enter a filename: ") do |q|
143
+ q.confirm = "Are you sure you want to overwrite <%= @answer %>? "
144
+ end
145
+ assert_equal("junk.txt", answer)
146
+ assert_equal( "Enter a filename: " +
147
+ "Are you sure you want to overwrite junk.txt? ",
148
+ @output.string )
149
+ end
150
+
82
151
  def test_defaults
83
152
  @input << "\nNo Comment\n"
84
153
  @input.rewind
@@ -159,6 +228,28 @@ class TestHighLine < Test::Unit::TestCase
159
228
  assert_equal("Edward", answer.middle)
160
229
  end
161
230
 
231
+ def test_no_echo
232
+ @input << "password\r"
233
+ @input.rewind
234
+
235
+ answer = @terminal.ask("Please enter your password: ") do |q|
236
+ q.echo = false
237
+ end
238
+ assert_equal("password", answer)
239
+ assert_equal("Please enter your password: \n", @output.string)
240
+
241
+ @input.rewind
242
+ @output.truncate(@output.rewind)
243
+
244
+ answer = @terminal.ask("Pick a letter or number: ") do |q|
245
+ q.character = true
246
+ q.echo = false
247
+ end
248
+ assert_equal("p", answer)
249
+ assert_equal("a", @input.getc.chr)
250
+ assert_equal("Pick a letter or number: \n", @output.string)
251
+ end
252
+
162
253
  def test_paging
163
254
  @terminal.page_at = 22
164
255
 
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.10
3
3
  specification_version: 1
4
4
  name: highline
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.4.0
7
- date: 2005-05-07
6
+ version: 0.5.0
7
+ date: 2005-05-09
8
8
  summary: HighLine is a high-level line oriented console interface.
9
9
  require_paths:
10
10
  - lib
@@ -31,7 +31,9 @@ authors:
31
31
  files:
32
32
  - examples/ansi_colors.rb
33
33
  - examples/basic_usage.rb
34
+ - examples/menus.rb
34
35
  - examples/page_and_wrap.rb
36
+ - examples/password.rb
35
37
  - lib/highline.rb
36
38
  - lib/highline/import.rb
37
39
  - lib/highline/question.rb