keybox 1.0.0 → 1.1.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.
@@ -12,7 +12,7 @@ context "Keybox Password Generator Application" do
12
12
 
13
13
  specify "invalid options set the error message, exit 1 and have output on stderr" do
14
14
  kpg = Keybox::Application::PasswordGenerator.new(["--invalid-option"])
15
- kpg.stderr = StringIO.new
15
+ kpg.set_io(StringIO.new,StringIO.new,StringIO.new)
16
16
  begin
17
17
  kpg.run
18
18
  rescue SystemExit => se
@@ -24,14 +24,14 @@ context "Keybox Password Generator Application" do
24
24
 
25
25
  specify "can set the algorithm" do
26
26
  kpg = Keybox::Application::PasswordGenerator.new(["--alg", "pron"])
27
- kpg.stdout = StringIO.new
27
+ kpg.set_io(StringIO.new,StringIO.new)
28
28
  kpg.run
29
29
  kpg.options.algorithm.should == :pronounceable
30
30
  end
31
31
 
32
32
  specify "can specify the number of passwords created " do
33
33
  kpg = Keybox::Application::PasswordGenerator.new(["--num", "4"])
34
- kpg.stdout = StringIO.new
34
+ kpg.set_io(StringIO.new,StringIO.new)
35
35
  kpg.run
36
36
  kpg.options.number_to_generate.should_eql 4
37
37
  kpg.stdout.string.split(/\s+/).size.should == 4
@@ -39,7 +39,7 @@ context "Keybox Password Generator Application" do
39
39
 
40
40
  specify "help has output on stdout and exits 0" do
41
41
  kpg = Keybox::Application::PasswordGenerator.new(["--h"])
42
- kpg.stdout = StringIO.new
42
+ kpg.set_io(StringIO.new,StringIO.new)
43
43
  begin
44
44
  kpg.run
45
45
  rescue SystemExit => se
@@ -51,18 +51,18 @@ context "Keybox Password Generator Application" do
51
51
 
52
52
  specify "version has output on stdout and exits 0" do
53
53
  kpg = Keybox::Application::PasswordGenerator.new(["--version"])
54
- kpg.stdout = StringIO.new
54
+ kpg.set_io(StringIO.new,StringIO.new)
55
55
  begin
56
56
  kpg.run
57
57
  rescue SystemExit => se
58
58
  se.status.should_eql 0
59
59
  end
60
- kpg.stdout.string.should_satisfy { |msg| msg =~ /version 1.0.0/m }
60
+ kpg.stdout.string.should_satisfy { |msg| msg =~ /version 1/m }
61
61
  end
62
62
 
63
63
  specify "minimum length can be set and all generated passwords will have length >= minimum length" do
64
64
  kpg = Keybox::Application::PasswordGenerator.new(["--min", "4"])
65
- kpg.stdout = StringIO.new
65
+ kpg.set_io(StringIO.new,StringIO.new)
66
66
  kpg.run
67
67
 
68
68
  kpg.options.min_length.should_eql 4
@@ -73,7 +73,7 @@ context "Keybox Password Generator Application" do
73
73
 
74
74
  specify "maximum length can be set and all generated passwords will have length <= maximum length" do
75
75
  kpg = Keybox::Application::PasswordGenerator.new(["--max", "4", "--min", "3"])
76
- kpg.stdout = StringIO.new
76
+ kpg.set_io(StringIO.new,StringIO.new,StringIO.new)
77
77
  kpg.run
78
78
 
79
79
  kpg.options.max_length.should_eql 4
@@ -84,7 +84,7 @@ context "Keybox Password Generator Application" do
84
84
 
85
85
  specify "setting an invalid required symbol set exits 1 and outputs data on stderr" do
86
86
  kpg = Keybox::Application::PasswordGenerator.new(["--req","bunk"])
87
- kpg.stderr = StringIO.new
87
+ kpg.set_io(StringIO.new,StringIO.new,StringIO.new)
88
88
  begin
89
89
  kpg.run
90
90
  rescue SystemExit => se
@@ -97,7 +97,7 @@ context "Keybox Password Generator Application" do
97
97
 
98
98
  specify "setting an invalid use symbol set exits 1 and outputs data on stderr" do
99
99
  kpg = Keybox::Application::PasswordGenerator.new(["--use","bunk"])
100
- kpg.stderr = StringIO.new
100
+ kpg.set_io(StringIO.new,StringIO.new,StringIO.new)
101
101
  begin
102
102
  kpg.run
103
103
  rescue SystemExit => se
@@ -110,7 +110,7 @@ context "Keybox Password Generator Application" do
110
110
 
111
111
  specify "setting an valid use symbol works" do
112
112
  kpg = Keybox::Application::PasswordGenerator.new(["--use","l"])
113
- kpg.stdout = StringIO.new
113
+ kpg.set_io(StringIO.new,StringIO.new,StringIO.new)
114
114
  kpg.run
115
115
  kpg.options.use_symbols.should_include Keybox::SymbolSet::LOWER_ASCII
116
116
  kpg.stdout.string.split(/\s+/).size.should == 6
@@ -118,7 +118,7 @@ context "Keybox Password Generator Application" do
118
118
 
119
119
  specify "setting an valid required symbol works" do
120
120
  kpg = Keybox::Application::PasswordGenerator.new(["--req","l"])
121
- kpg.stdout = StringIO.new
121
+ kpg.set_io(StringIO.new,StringIO.new,StringIO.new)
122
122
  kpg.run
123
123
  kpg.options.require_symbols.should_include Keybox::SymbolSet::LOWER_ASCII
124
124
  kpg.stdout.string.split(/\s+/).size.should == 6
@@ -0,0 +1,704 @@
1
+ #!/usr/local/bin/ruby -w
2
+
3
+ # highline.rb
4
+ #
5
+ # Created by James Edward Gray II on 2005-04-26.
6
+ # Copyright 2005 Gray Productions. All rights reserved.
7
+ #
8
+ # See HighLine for documentation.
9
+ #
10
+ # This is Free Software. See LICENSE and COPYING for details.
11
+
12
+ require "highline/system_extensions"
13
+ require "highline/question"
14
+ require "highline/menu"
15
+ require "highline/color_scheme"
16
+ require "erb"
17
+ require "optparse"
18
+ require "stringio"
19
+ require "abbrev"
20
+
21
+ #
22
+ # A HighLine object is a "high-level line oriented" shell over an input and an
23
+ # output stream. HighLine simplifies common console interaction, effectively
24
+ # replacing puts() and gets(). User code can simply specify the question to ask
25
+ # and any details about user interaction, then leave the rest of the work to
26
+ # HighLine. When HighLine.ask() returns, you'll have the answer you requested,
27
+ # even if HighLine had to ask many times, validate results, perform range
28
+ # checking, convert types, etc.
29
+ #
30
+ class HighLine
31
+ # The version of the installed library.
32
+ VERSION = "1.2.6".freeze
33
+
34
+ # An internal HighLine error. User code does not need to trap this.
35
+ class QuestionError < StandardError
36
+ # do nothing, just creating a unique error type
37
+ end
38
+
39
+ # The setting used to disable color output.
40
+ @@use_color = true
41
+
42
+ # Pass +false+ to _setting_ to turn off HighLine's color escapes.
43
+ def self.use_color=( setting )
44
+ @@use_color = setting
45
+ end
46
+
47
+ # Returns true if HighLine is currently using color escapes.
48
+ def self.use_color?
49
+ @@use_color
50
+ end
51
+
52
+ # The setting used to control color schemes.
53
+ @@color_scheme = nil
54
+
55
+ # Pass ColorScheme to _setting_ to turn set a HighLine color scheme.
56
+ def self.color_scheme=( setting )
57
+ @@color_scheme = setting
58
+ end
59
+
60
+ # Returns the current color scheme.
61
+ def self.color_scheme
62
+ @@color_scheme
63
+ end
64
+
65
+ # Returns +true+ if HighLine is currently using a color scheme.
66
+ def self.using_color_scheme?
67
+ not @@color_scheme.nil?
68
+ end
69
+
70
+ #
71
+ # Embed in a String to clear all previous ANSI sequences. This *MUST* be
72
+ # done before the program exits!
73
+ #
74
+ CLEAR = "\e[0m"
75
+ # An alias for CLEAR.
76
+ RESET = CLEAR
77
+ # Erase the current line of terminal output.
78
+ ERASE_LINE = "\e[K"
79
+ # The start of an ANSI bold sequence.
80
+ BOLD = "\e[1m"
81
+ # The start of an ANSI dark sequence. (Terminal support uncommon.)
82
+ DARK = "\e[2m"
83
+ # The start of an ANSI underline sequence.
84
+ UNDERLINE = "\e[4m"
85
+ # An alias for UNDERLINE.
86
+ UNDERSCORE = UNDERLINE
87
+ # The start of an ANSI blink sequence. (Terminal support uncommon.)
88
+ BLINK = "\e[5m"
89
+ # The start of an ANSI reverse sequence.
90
+ REVERSE = "\e[7m"
91
+ # The start of an ANSI concealed sequence. (Terminal support uncommon.)
92
+ CONCEALED = "\e[8m"
93
+
94
+ # Set the terminal's foreground ANSI color to black.
95
+ BLACK = "\e[30m"
96
+ # Set the terminal's foreground ANSI color to red.
97
+ RED = "\e[31m"
98
+ # Set the terminal's foreground ANSI color to green.
99
+ GREEN = "\e[32m"
100
+ # Set the terminal's foreground ANSI color to yellow.
101
+ YELLOW = "\e[33m"
102
+ # Set the terminal's foreground ANSI color to blue.
103
+ BLUE = "\e[34m"
104
+ # Set the terminal's foreground ANSI color to magenta.
105
+ MAGENTA = "\e[35m"
106
+ # Set the terminal's foreground ANSI color to cyan.
107
+ CYAN = "\e[36m"
108
+ # Set the terminal's foreground ANSI color to white.
109
+ WHITE = "\e[37m"
110
+
111
+ # Set the terminal's background ANSI color to black.
112
+ ON_BLACK = "\e[40m"
113
+ # Set the terminal's background ANSI color to red.
114
+ ON_RED = "\e[41m"
115
+ # Set the terminal's background ANSI color to green.
116
+ ON_GREEN = "\e[42m"
117
+ # Set the terminal's background ANSI color to yellow.
118
+ ON_YELLOW = "\e[43m"
119
+ # Set the terminal's background ANSI color to blue.
120
+ ON_BLUE = "\e[44m"
121
+ # Set the terminal's background ANSI color to magenta.
122
+ ON_MAGENTA = "\e[45m"
123
+ # Set the terminal's background ANSI color to cyan.
124
+ ON_CYAN = "\e[46m"
125
+ # Set the terminal's background ANSI color to white.
126
+ ON_WHITE = "\e[47m"
127
+
128
+ #
129
+ # Create an instance of HighLine, connected to the streams _input_
130
+ # and _output_.
131
+ #
132
+ def initialize( input = $stdin, output = $stdout,
133
+ wrap_at = nil, page_at = nil )
134
+ @input = input
135
+ @output = output
136
+
137
+ self.wrap_at = wrap_at
138
+ self.page_at = page_at
139
+
140
+ @question = nil
141
+ @answer = nil
142
+ @menu = nil
143
+ @header = nil
144
+ @prompt = nil
145
+ @gather = nil
146
+ @answers = nil
147
+ @key = nil
148
+ end
149
+
150
+ include HighLine::SystemExtensions
151
+
152
+ # The current column setting for wrapping output.
153
+ attr_reader :wrap_at
154
+ # The current row setting for paging output.
155
+ attr_reader :page_at
156
+
157
+ #
158
+ # A shortcut to HighLine.ask() a question that only accepts "yes" or "no"
159
+ # answers ("y" and "n" are allowed) and returns +true+ or +false+
160
+ # (+true+ for "yes"). If provided a +true+ value, _character_ will cause
161
+ # HighLine to fetch a single character response.
162
+ #
163
+ # Raises EOFError if input is exhausted.
164
+ #
165
+ def agree( yes_or_no_question, character = nil )
166
+ ask(yes_or_no_question, lambda { |yn| yn.downcase[0] == ?y}) do |q|
167
+ q.validate = /\Ay(?:es)?|no?\Z/i
168
+ q.responses[:not_valid] = 'Please enter "yes" or "no".'
169
+ q.responses[:ask_on_error] = :question
170
+ q.character = character
171
+ end
172
+ end
173
+
174
+ #
175
+ # This method is the primary interface for user input. Just provide a
176
+ # _question_ to ask the user, the _answer_type_ you want returned, and
177
+ # optionally a code block setting up details of how you want the question
178
+ # handled. See HighLine.say() for details on the format of _question_, and
179
+ # HighLine::Question for more information about _answer_type_ and what's
180
+ # valid in the code block.
181
+ #
182
+ # If <tt>@question</tt> is set before ask() is called, parameters are
183
+ # ignored and that object (must be a HighLine::Question) is used to drive
184
+ # the process instead.
185
+ #
186
+ # Raises EOFError if input is exhausted.
187
+ #
188
+ def ask( question, answer_type = String, &details ) # :yields: question
189
+ @question ||= Question.new(question, answer_type, &details)
190
+
191
+ return gather if @question.gather
192
+
193
+ # readline() needs to handle it's own output
194
+ say(@question) unless @question.readline
195
+ begin
196
+ @answer = @question.answer_or_default(get_response)
197
+ unless @question.valid_answer?(@answer)
198
+ explain_error(:not_valid)
199
+ raise QuestionError
200
+ end
201
+
202
+ @answer = @question.convert(@answer)
203
+
204
+ if @question.in_range?(@answer)
205
+ if @question.confirm
206
+ # need to add a layer of scope to ask a question inside a
207
+ # question, without destroying instance data
208
+ context_change = self.class.new(@input, @output, @wrap_at, @page_at)
209
+ if @question.confirm == true
210
+ confirm_question = "Are you sure? "
211
+ else
212
+ # evaluate ERb under initial scope, so it will have
213
+ # access to @question and @answer
214
+ template = ERB.new(@question.confirm, nil, "%")
215
+ confirm_question = template.result(binding)
216
+ end
217
+ unless context_change.agree(confirm_question)
218
+ explain_error(nil)
219
+ raise QuestionError
220
+ end
221
+ end
222
+
223
+ @answer
224
+ else
225
+ explain_error(:not_in_range)
226
+ raise QuestionError
227
+ end
228
+ rescue QuestionError
229
+ retry
230
+ rescue ArgumentError
231
+ explain_error(:invalid_type)
232
+ retry
233
+ rescue Question::NoAutoCompleteMatch
234
+ explain_error(:no_completion)
235
+ retry
236
+ rescue NameError
237
+ raise if $!.is_a?(NoMethodError)
238
+ explain_error(:ambiguous_completion)
239
+ retry
240
+ ensure
241
+ @question = nil # Reset Question object.
242
+ end
243
+ end
244
+
245
+ #
246
+ # This method is HighLine's menu handler. For simple usage, you can just
247
+ # pass all the menu items you wish to display. At that point, choose() will
248
+ # build and display a menu, walk the user through selection, and return
249
+ # their choice amoung the provided items. You might use this in a case
250
+ # statement for quick and dirty menus.
251
+ #
252
+ # However, choose() is capable of much more. If provided, a block will be
253
+ # passed a HighLine::Menu object to configure. Using this method, you can
254
+ # customize all the details of menu handling from index display, to building
255
+ # a complete shell-like menuing system. See HighLine::Menu for all the
256
+ # methods it responds to.
257
+ #
258
+ # Raises EOFError if input is exhausted.
259
+ #
260
+ def choose( *items, &details )
261
+ @menu = @question = Menu.new(&details)
262
+ @menu.choices(*items) unless items.empty?
263
+
264
+ # Set _answer_type_ so we can double as the Question for ask().
265
+ @menu.answer_type = if @menu.shell
266
+ lambda do |command| # shell-style selection
267
+ first_word = command.to_s.split.first || ""
268
+
269
+ options = @menu.options
270
+ options.extend(OptionParser::Completion)
271
+ answer = options.complete(first_word)
272
+
273
+ if answer.nil?
274
+ raise Question::NoAutoCompleteMatch
275
+ end
276
+
277
+ [answer.last, command.sub(/^\s*#{first_word}\s*/, "")]
278
+ end
279
+ else
280
+ @menu.options # normal menu selection, by index or name
281
+ end
282
+
283
+ # Provide hooks for ERb layouts.
284
+ @header = @menu.header
285
+ @prompt = @menu.prompt
286
+
287
+ if @menu.shell
288
+ selected = ask("Ignored", @menu.answer_type)
289
+ @menu.select(self, *selected)
290
+ else
291
+ selected = ask("Ignored", @menu.answer_type)
292
+ @menu.select(self, selected)
293
+ end
294
+ end
295
+
296
+ #
297
+ # This method provides easy access to ANSI color sequences, without the user
298
+ # needing to remember to CLEAR at the end of each sequence. Just pass the
299
+ # _string_ to color, followed by a list of _colors_ you would like it to be
300
+ # affected by. The _colors_ can be HighLine class constants, or symbols
301
+ # (:blue for BLUE, for example). A CLEAR will automatically be embedded to
302
+ # the end of the returned String.
303
+ #
304
+ # This method returns the original _string_ unchanged if HighLine::use_color?
305
+ # is +false+.
306
+ #
307
+ def color( string, *colors )
308
+ return string unless self.class.use_color?
309
+
310
+ colors.map! do |c|
311
+ if self.class.using_color_scheme? and self.class.color_scheme.include? c
312
+ self.class.color_scheme[c]
313
+ elsif c.is_a? Symbol
314
+ self.class.const_get(c.to_s.upcase)
315
+ else
316
+ c
317
+ end
318
+ end
319
+ "#{colors.flatten.join}#{string}#{CLEAR}"
320
+ end
321
+
322
+ #
323
+ # This method is a utility for quickly and easily laying out lists. It can
324
+ # be accessed within ERb replacements of any text that will be sent to the
325
+ # user.
326
+ #
327
+ # The only required parameter is _items_, which should be the Array of items
328
+ # to list. A specified _mode_ controls how that list is formed and _option_
329
+ # has different effects, depending on the _mode_. Recognized modes are:
330
+ #
331
+ # <tt>:columns_across</tt>:: _items_ will be placed in columns, flowing
332
+ # from left to right. If given, _option_ is the
333
+ # number of columns to be used. When absent,
334
+ # columns will be determined based on _wrap_at_
335
+ # or a default of 80 characters.
336
+ # <tt>:columns_down</tt>:: Identical to <tt>:columns_across</tt>, save
337
+ # flow goes down.
338
+ # <tt>:inline</tt>:: All _items_ are placed on a single line. The
339
+ # last two _items_ are separated by _option_ or
340
+ # a default of " or ". All other _items_ are
341
+ # separated by ", ".
342
+ # <tt>:rows</tt>:: The default mode. Each of the _items_ is
343
+ # placed on it's own line. The _option_
344
+ # parameter is ignored in this mode.
345
+ #
346
+ # Each member of the _items_ Array is passed through ERb and thus can contain
347
+ # their own expansions. Color escape expansions do not contribute to the
348
+ # final field width.
349
+ #
350
+ def list( items, mode = :rows, option = nil )
351
+ items = items.to_ary.map do |item|
352
+ ERB.new(item, nil, "%").result(binding)
353
+ end
354
+
355
+ case mode
356
+ when :inline
357
+ option = " or " if option.nil?
358
+
359
+ case items.size
360
+ when 0
361
+ ""
362
+ when 1
363
+ items.first
364
+ when 2
365
+ "#{items.first}#{option}#{items.last}"
366
+ else
367
+ items[0..-2].join(", ") + "#{option}#{items.last}"
368
+ end
369
+ when :columns_across, :columns_down
370
+ max_length = actual_length(
371
+ items.max { |a, b| actual_length(a) <=> actual_length(b) }
372
+ )
373
+
374
+ if option.nil?
375
+ limit = @wrap_at || 80
376
+ option = (limit + 2) / (max_length + 2)
377
+ end
378
+
379
+ items = items.map do |item|
380
+ pad = max_length + (item.length - actual_length(item))
381
+ "%-#{pad}s" % item
382
+ end
383
+ row_count = (items.size / option.to_f).ceil
384
+
385
+ if mode == :columns_across
386
+ rows = Array.new(row_count) { Array.new }
387
+ items.each_with_index do |item, index|
388
+ rows[index / option] << item
389
+ end
390
+
391
+ rows.map { |row| row.join(" ") + "\n" }.join
392
+ else
393
+ columns = Array.new(option) { Array.new }
394
+ items.each_with_index do |item, index|
395
+ columns[index / row_count] << item
396
+ end
397
+
398
+ list = ""
399
+ columns.first.size.times do |index|
400
+ list << columns.map { |column| column[index] }.
401
+ compact.join(" ") + "\n"
402
+ end
403
+ list
404
+ end
405
+ else
406
+ items.map { |i| "#{i}\n" }.join
407
+ end
408
+ end
409
+
410
+ #
411
+ # The basic output method for HighLine objects. If the provided _statement_
412
+ # ends with a space or tab character, a newline will not be appended (output
413
+ # will be flush()ed). All other cases are passed straight to Kernel.puts().
414
+ #
415
+ # The _statement_ parameter is processed as an ERb template, supporting
416
+ # embedded Ruby code. The template is evaluated with a binding inside
417
+ # the HighLine instance, providing easy access to the ANSI color constants
418
+ # and the HighLine.color() method.
419
+ #
420
+ def say( statement )
421
+ statement = statement.to_str
422
+ return unless statement.length > 0
423
+
424
+ template = ERB.new(statement, nil, "%")
425
+ statement = template.result(binding)
426
+
427
+ statement = wrap(statement) unless @wrap_at.nil?
428
+ statement = page_print(statement) unless @page_at.nil?
429
+
430
+ if statement[-1, 1] == " " or statement[-1, 1] == "\t"
431
+ @output.print(statement)
432
+ @output.flush
433
+ else
434
+ @output.puts(statement)
435
+ end
436
+ end
437
+
438
+ #
439
+ # Set to an integer value to cause HighLine to wrap output lines at the
440
+ # indicated character limit. When +nil+, the default, no wrapping occurs. If
441
+ # set to <tt>:auto</tt>, HighLine will attempt to determing the columns
442
+ # available for the <tt>@output</tt> or use a sensible default.
443
+ #
444
+ def wrap_at=( setting )
445
+ @wrap_at = setting == :auto ? output_cols : setting
446
+ end
447
+
448
+ #
449
+ # Set to an integer value to cause HighLine to page output lines over the
450
+ # indicated line limit. When +nil+, the default, no paging occurs. If
451
+ # set to <tt>:auto</tt>, HighLine will attempt to determing the rows available
452
+ # for the <tt>@output</tt> or use a sensible default.
453
+ #
454
+ def page_at=( setting )
455
+ @page_at = setting == :auto ? output_rows : setting
456
+ end
457
+
458
+ #
459
+ # Returns the number of columns for the console, or a default it they cannot
460
+ # be determined.
461
+ #
462
+ def output_cols
463
+ return 80 unless @output.tty?
464
+ terminal_size.first
465
+ rescue
466
+ return 80
467
+ end
468
+
469
+ #
470
+ # Returns the number of rows for the console, or a default if they cannot be
471
+ # determined.
472
+ #
473
+ def output_rows
474
+ return 24 unless @output.tty?
475
+ terminal_size.last
476
+ rescue
477
+ return 24
478
+ end
479
+
480
+ private
481
+
482
+ #
483
+ # A helper method for sending the output stream and error and repeat
484
+ # of the question.
485
+ #
486
+ def explain_error( error )
487
+ say(@question.responses[error]) unless error.nil?
488
+ if @question.responses[:ask_on_error] == :question
489
+ say(@question)
490
+ elsif @question.responses[:ask_on_error]
491
+ say(@question.responses[:ask_on_error])
492
+ end
493
+ end
494
+
495
+ #
496
+ # Collects an Array/Hash full of answers as described in
497
+ # HighLine::Question.gather().
498
+ #
499
+ # Raises EOFError if input is exhausted.
500
+ #
501
+ def gather( )
502
+ @gather = @question.gather
503
+ @answers = [ ]
504
+ original_question = @question
505
+
506
+ @question.gather = false
507
+
508
+ case @gather
509
+ when Integer
510
+ @answers << ask(@question)
511
+ @gather -= 1
512
+
513
+ original_question.question = ""
514
+ until @gather.zero?
515
+ @question = original_question
516
+ @answers << ask(@question)
517
+ @gather -= 1
518
+ end
519
+ when String, Regexp
520
+ @answers << ask(@question)
521
+
522
+ original_question.question = ""
523
+ until (@gather.is_a?(String) and @answers.last.to_s == @gather) or
524
+ (@gather.is_a?(Regexp) and @answers.last.to_s =~ @gather)
525
+ @question = original_question
526
+ @answers << ask(@question)
527
+ end
528
+
529
+ @answers.pop
530
+ when Hash
531
+ @answers = { }
532
+ @gather.keys.sort.each do |key|
533
+ @question = original_question
534
+ @key = key
535
+ @answers[key] = ask(@question)
536
+ end
537
+ end
538
+
539
+ @answers
540
+ end
541
+
542
+ #
543
+ # Read a line of input from the input stream and process whitespace as
544
+ # requested by the Question object.
545
+ #
546
+ # If Question's _readline_ property is set, that library will be used to
547
+ # fetch input. *WARNING*: This ignores the currently set input stream.
548
+ #
549
+ # Raises EOFError if input is exhausted.
550
+ #
551
+ def get_line( )
552
+ if @question.readline
553
+ require "readline" # load only if needed
554
+
555
+ # capture say()'s work in a String to feed to readline()
556
+ old_output = @output
557
+ @output = StringIO.new
558
+ say(@question)
559
+ question = @output.string
560
+ @output = old_output
561
+
562
+ # prep auto-completion
563
+ completions = @question.selection.abbrev
564
+ Readline.completion_proc = lambda { |string| completions[string] }
565
+
566
+ # work-around ugly readline() warnings
567
+ old_verbose = $VERBOSE
568
+ $VERBOSE = nil
569
+ answer = @question.change_case(
570
+ @question.remove_whitespace(
571
+ Readline.readline(question, true) ) )
572
+ $VERBOSE = old_verbose
573
+
574
+ answer
575
+ else
576
+ raise EOFError, "The input stream is exhausted." if @input.eof?
577
+
578
+ @question.change_case(@question.remove_whitespace(@input.gets))
579
+ end
580
+ end
581
+
582
+ #
583
+ # Return a line or character of input, as requested for this question.
584
+ # Character input will be returned as a single character String,
585
+ # not an Integer.
586
+ #
587
+ # This question's _first_answer_ will be returned instead of input, if set.
588
+ #
589
+ # Raises EOFError if input is exhausted.
590
+ #
591
+ def get_response( )
592
+ return @question.first_answer if @question.first_answer?
593
+
594
+ if @question.character.nil?
595
+ if @question.echo == true and @question.limit.nil?
596
+ get_line
597
+ else
598
+ raw_no_echo_mode if stty = CHARACTER_MODE == "stty"
599
+
600
+ line = ""
601
+ begin
602
+ while character = (stty ? @input.getc : get_character(@input))
603
+ line << character.chr
604
+ # looking for carriage return (decimal 13) or
605
+ # newline (decimal 10) in raw input
606
+ break if character == 13 or character == 10 or
607
+ (@question.limit and line.size == @question.limit)
608
+ @output.print(@question.echo) if @question.echo != false
609
+ end
610
+ if @question.overwrite
611
+ @output.print("\r#{ERASE_LINE}")
612
+ @output.flush
613
+ else
614
+ say("\n")
615
+ end
616
+ ensure
617
+ restore_mode if stty
618
+ end
619
+
620
+ @question.change_case(@question.remove_whitespace(line))
621
+ end
622
+ elsif @question.character == :getc
623
+ @question.change_case(@input.getc.chr)
624
+ else
625
+ response = get_character(@input).chr
626
+ if @question.overwrite
627
+ @output.print("\r#{ERASE_LINE}")
628
+ @output.flush
629
+ else
630
+ echo = if @question.echo == true
631
+ response
632
+ elsif @question.echo != false
633
+ @question.echo
634
+ else
635
+ ""
636
+ end
637
+ say("#{echo}\n")
638
+ end
639
+ @question.change_case(response)
640
+ end
641
+ end
642
+
643
+ #
644
+ # Page print a series of at most _page_at_ lines for _output_. After each
645
+ # page is printed, HighLine will pause until the user presses enter/return
646
+ # then display the next page of data.
647
+ #
648
+ # Note that the final page of _output_ is *not* printed, but returned
649
+ # instead. This is to support any special handling for the final sequence.
650
+ #
651
+ def page_print( output )
652
+ lines = output.scan(/[^\n]*\n?/)
653
+ while lines.size > @page_at
654
+ @output.puts lines.slice!(0...@page_at).join
655
+ @output.puts
656
+ # Return last line if user wants to abort paging
657
+ return (["...\n"] + lines.slice(-2,1)).join unless continue_paging?
658
+ end
659
+ return lines.join
660
+ end
661
+
662
+ #
663
+ # Ask user if they wish to continue paging output. Allows them to type "q" to
664
+ # cancel the paging process.
665
+ #
666
+ def continue_paging?
667
+ command = HighLine.new(@input, @output).ask(
668
+ "-- press enter/return to continue or q to stop -- "
669
+ ) { |q| q.character = true }
670
+ command !~ /\A[qQ]\Z/ # Only continue paging if Q was not hit.
671
+ end
672
+
673
+ #
674
+ # Wrap a sequence of _lines_ at _wrap_at_ characters per line. Existing
675
+ # newlines will not be affected by this process, but additional newlines
676
+ # may be added.
677
+ #
678
+ def wrap( lines )
679
+ wrapped = [ ]
680
+ lines.each do |line|
681
+ while line =~ /([^\n]{#{@wrap_at + 1},})/
682
+ search = $1.dup
683
+ replace = $1.dup
684
+ if index = replace.rindex(" ", @wrap_at)
685
+ replace[index, 1] = "\n"
686
+ replace.sub!(/\n[ \t]+/, "\n")
687
+ line.sub!(search, replace)
688
+ else
689
+ line[@wrap_at, 0] = "\n"
690
+ end
691
+ end
692
+ wrapped << line
693
+ end
694
+ return wrapped.join
695
+ end
696
+
697
+ #
698
+ # Returns the length of the passed +string_with_escapes+, minus and color
699
+ # sequence escapes.
700
+ #
701
+ def actual_length( string_with_escapes )
702
+ string_with_escapes.gsub(/\e\[\d{1,2}m/, "").length
703
+ end
704
+ end