highline 0.4.0 → 0.5.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 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