motomike-bnr_tools 0.0.2

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