malysz87-highline 1.5.2 → 1.5.4

Sign up to get free protection for your applications and to get access to all the features.
data/lib/highline.rb ADDED
@@ -0,0 +1,757 @@
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.5.0".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. A block can be provided
177
+ # to further configure the question as in HighLine.ask()
178
+ #
179
+ # Raises EOFError if input is exhausted.
180
+ #
181
+ def agree( yes_or_no_question, character = nil )
182
+ ask(yes_or_no_question, lambda { |yn| yn.downcase[0] == ?y}) do |q|
183
+ q.validate = /\Ay(?:es)?|no?\Z/i
184
+ q.responses[:not_valid] = 'Please enter "yes" or "no".'
185
+ q.responses[:ask_on_error] = :question
186
+ q.character = character
187
+
188
+ yield q if block_given?
189
+ end
190
+ end
191
+
192
+ #
193
+ # This method is the primary interface for user input. Just provide a
194
+ # _question_ to ask the user, the _answer_type_ you want returned, and
195
+ # optionally a code block setting up details of how you want the question
196
+ # handled. See HighLine.say() for details on the format of _question_, and
197
+ # HighLine::Question for more information about _answer_type_ and what's
198
+ # valid in the code block.
199
+ #
200
+ # If <tt>@question</tt> is set before ask() is called, parameters are
201
+ # ignored and that object (must be a HighLine::Question) is used to drive
202
+ # the process instead.
203
+ #
204
+ # Raises EOFError if input is exhausted.
205
+ #
206
+ def ask( question, answer_type = String, &details ) # :yields: question
207
+ @question ||= Question.new(question, answer_type, &details)
208
+
209
+ return gather if @question.gather
210
+
211
+ # readline() needs to handle it's own output, but readline only supports
212
+ # full line reading. Therefore if @question.echo is anything but true,
213
+ # the prompt will not be issued. And we have to account for that now.
214
+ say(@question) unless (@question.readline and @question.echo == true)
215
+ begin
216
+ @answer = @question.answer_or_default(get_response)
217
+ unless @question.valid_answer?(@answer)
218
+ explain_error(:not_valid)
219
+ raise QuestionError
220
+ end
221
+
222
+ @answer = @question.convert(@answer)
223
+
224
+ if @question.in_range?(@answer)
225
+ if @question.confirm
226
+ # need to add a layer of scope to ask a question inside a
227
+ # question, without destroying instance data
228
+ context_change = self.class.new(@input, @output, @wrap_at, @page_at)
229
+ if @question.confirm == true
230
+ confirm_question = "Are you sure? "
231
+ else
232
+ # evaluate ERb under initial scope, so it will have
233
+ # access to @question and @answer
234
+ template = ERB.new(@question.confirm, nil, "%")
235
+ confirm_question = template.result(binding)
236
+ end
237
+ unless context_change.agree(confirm_question)
238
+ explain_error(nil)
239
+ raise QuestionError
240
+ end
241
+ end
242
+
243
+ @answer
244
+ else
245
+ explain_error(:not_in_range)
246
+ raise QuestionError
247
+ end
248
+ rescue QuestionError
249
+ retry
250
+ rescue ArgumentError
251
+ explain_error(:invalid_type)
252
+ retry
253
+ rescue Question::NoAutoCompleteMatch
254
+ explain_error(:no_completion)
255
+ retry
256
+ rescue Question::NotEnoughAnswers
257
+ explain_error(:not_enough_answers)
258
+ retry
259
+ rescue NameError
260
+ raise if $!.is_a?(NoMethodError)
261
+ explain_error(:ambiguous_completion)
262
+ retry
263
+ ensure
264
+ @question = nil # Reset Question object.
265
+ end
266
+ end
267
+
268
+ #
269
+ # This method is HighLine's menu handler. For simple usage, you can just
270
+ # pass all the menu items you wish to display. At that point, choose() will
271
+ # build and display a menu, walk the user through selection, and return
272
+ # their choice amoung the provided items. You might use this in a case
273
+ # statement for quick and dirty menus.
274
+ #
275
+ # However, choose() is capable of much more. If provided, a block will be
276
+ # passed a HighLine::Menu object to configure. Using this method, you can
277
+ # customize all the details of menu handling from index display, to building
278
+ # a complete shell-like menuing system. See HighLine::Menu for all the
279
+ # methods it responds to.
280
+ #
281
+ # Raises EOFError if input is exhausted.
282
+ #
283
+ def choose( *items, &details )
284
+ @menu = @question = Menu.new(&details)
285
+ @menu.choices(*items) unless items.empty?
286
+
287
+ # Set _answer_type_ so we can double as the Question for ask().
288
+ @menu.answer_type = if @menu.shell
289
+ lambda do |command| # shell-style selection
290
+ first_word = command.to_s.split.first || ""
291
+
292
+ options = @menu.options
293
+ options.extend(OptionParser::Completion)
294
+ answer = options.complete(first_word)
295
+
296
+ if answer.nil?
297
+ raise Question::NoAutoCompleteMatch
298
+ end
299
+
300
+ [answer.last, command.sub(/^\s*#{first_word}\s*/, "")]
301
+ end
302
+ else
303
+ @menu.options # normal menu selection, by index or name
304
+ end
305
+
306
+ # Provide hooks for ERb layouts.
307
+ @header = @menu.header
308
+ @prompt = @menu.prompt
309
+
310
+ if @menu.shell
311
+ selected = ask("Ignored", @menu.answer_type)
312
+ @menu.select(self, *selected)
313
+ else
314
+ selected = ask("Ignored", @menu.answer_type)
315
+ @menu.select(self, selected)
316
+ end
317
+ end
318
+
319
+ #
320
+ # This method provides easy access to ANSI color sequences, without the user
321
+ # needing to remember to CLEAR at the end of each sequence. Just pass the
322
+ # _string_ to color, followed by a list of _colors_ you would like it to be
323
+ # affected by. The _colors_ can be HighLine class constants, or symbols
324
+ # (:blue for BLUE, for example). A CLEAR will automatically be embedded to
325
+ # the end of the returned String.
326
+ #
327
+ # This method returns the original _string_ unchanged if HighLine::use_color?
328
+ # is +false+.
329
+ #
330
+ def color( string, *colors )
331
+ return string unless self.class.use_color?
332
+
333
+ colors.map! do |c|
334
+ if self.class.using_color_scheme? and self.class.color_scheme.include? c
335
+ self.class.color_scheme[c]
336
+ elsif c.is_a? Symbol
337
+ self.class.const_get(c.to_s.upcase)
338
+ else
339
+ c
340
+ end
341
+ end
342
+ "#{colors.flatten.join}#{string}#{CLEAR}"
343
+ end
344
+
345
+ #
346
+ # This method is a utility for quickly and easily laying out lists. It can
347
+ # be accessed within ERb replacements of any text that will be sent to the
348
+ # user.
349
+ #
350
+ # The only required parameter is _items_, which should be the Array of items
351
+ # to list. A specified _mode_ controls how that list is formed and _option_
352
+ # has different effects, depending on the _mode_. Recognized modes are:
353
+ #
354
+ # <tt>:columns_across</tt>:: _items_ will be placed in columns, flowing
355
+ # from left to right. If given, _option_ is the
356
+ # number of columns to be used. When absent,
357
+ # columns will be determined based on _wrap_at_
358
+ # or a default of 80 characters.
359
+ # <tt>:columns_down</tt>:: Identical to <tt>:columns_across</tt>, save
360
+ # flow goes down.
361
+ # <tt>:inline</tt>:: All _items_ are placed on a single line. The
362
+ # last two _items_ are separated by _option_ or
363
+ # a default of " or ". All other _items_ are
364
+ # separated by ", ".
365
+ # <tt>:rows</tt>:: The default mode. Each of the _items_ is
366
+ # placed on it's own line. The _option_
367
+ # parameter is ignored in this mode.
368
+ #
369
+ # Each member of the _items_ Array is passed through ERb and thus can contain
370
+ # their own expansions. Color escape expansions do not contribute to the
371
+ # final field width.
372
+ #
373
+ def list( items, mode = :rows, option = nil )
374
+ items = items.to_ary.map do |item|
375
+ ERB.new(item, nil, "%").result(binding)
376
+ end
377
+
378
+ case mode
379
+ when :inline
380
+ option = " or " if option.nil?
381
+
382
+ case items.size
383
+ when 0
384
+ ""
385
+ when 1
386
+ items.first
387
+ when 2
388
+ "#{items.first}#{option}#{items.last}"
389
+ else
390
+ items[0..-2].join(", ") + "#{option}#{items.last}"
391
+ end
392
+ when :columns_across, :columns_down
393
+ max_length = actual_length(
394
+ items.max { |a, b| actual_length(a) <=> actual_length(b) }
395
+ )
396
+
397
+ if option.nil?
398
+ limit = @wrap_at || 80
399
+ option = (limit + 2) / (max_length + 2)
400
+ end
401
+
402
+ items = items.map do |item|
403
+ pad = max_length + (item.length - actual_length(item))
404
+ "%-#{pad}s" % item
405
+ end
406
+ row_count = (items.size / option.to_f).ceil
407
+
408
+ if mode == :columns_across
409
+ rows = Array.new(row_count) { Array.new }
410
+ items.each_with_index do |item, index|
411
+ rows[index / option] << item
412
+ end
413
+
414
+ rows.map { |row| row.join(" ") + "\n" }.join
415
+ else
416
+ columns = Array.new(option) { Array.new }
417
+ items.each_with_index do |item, index|
418
+ columns[index / row_count] << item
419
+ end
420
+
421
+ list = ""
422
+ columns.first.size.times do |index|
423
+ list << columns.map { |column| column[index] }.
424
+ compact.join(" ") + "\n"
425
+ end
426
+ list
427
+ end
428
+ else
429
+ items.map { |i| "#{i}\n" }.join
430
+ end
431
+ end
432
+
433
+ #
434
+ # The basic output method for HighLine objects. If the provided _statement_
435
+ # ends with a space or tab character, a newline will not be appended (output
436
+ # will be flush()ed). All other cases are passed straight to Kernel.puts().
437
+ #
438
+ # The _statement_ parameter is processed as an ERb template, supporting
439
+ # embedded Ruby code. The template is evaluated with a binding inside
440
+ # the HighLine instance, providing easy access to the ANSI color constants
441
+ # and the HighLine.color() method.
442
+ #
443
+ def say( statement )
444
+ statement = statement.to_str
445
+ return unless statement.length > 0
446
+
447
+ template = ERB.new(statement, nil, "%")
448
+ statement = template.result(binding)
449
+
450
+ statement = wrap(statement) unless @wrap_at.nil?
451
+ statement = page_print(statement) unless @page_at.nil?
452
+
453
+ if statement[-1, 1] == " " or statement[-1, 1] == "\t"
454
+ @output.print(statement)
455
+ @output.flush
456
+ else
457
+ @output.puts(statement)
458
+ end
459
+ end
460
+
461
+ #
462
+ # Set to an integer value to cause HighLine to wrap output lines at the
463
+ # indicated character limit. When +nil+, the default, no wrapping occurs. If
464
+ # set to <tt>:auto</tt>, HighLine will attempt to determing the columns
465
+ # available for the <tt>@output</tt> or use a sensible default.
466
+ #
467
+ def wrap_at=( setting )
468
+ @wrap_at = setting == :auto ? output_cols : setting
469
+ end
470
+
471
+ #
472
+ # Set to an integer value to cause HighLine to page output lines over the
473
+ # indicated line limit. When +nil+, the default, no paging occurs. If
474
+ # set to <tt>:auto</tt>, HighLine will attempt to determing the rows available
475
+ # for the <tt>@output</tt> or use a sensible default.
476
+ #
477
+ def page_at=( setting )
478
+ @page_at = setting == :auto ? output_rows : setting
479
+ end
480
+
481
+ #
482
+ # Returns the number of columns for the console, or a default it they cannot
483
+ # be determined.
484
+ #
485
+ def output_cols
486
+ return 80 unless @output.tty?
487
+ terminal_size.first
488
+ rescue
489
+ return 80
490
+ end
491
+
492
+ #
493
+ # Returns the number of rows for the console, or a default if they cannot be
494
+ # determined.
495
+ #
496
+ def output_rows
497
+ return 24 unless @output.tty?
498
+ terminal_size.last
499
+ rescue
500
+ return 24
501
+ end
502
+
503
+ private
504
+
505
+ #
506
+ # A helper method for sending the output stream and error and repeat
507
+ # of the question.
508
+ #
509
+ def explain_error( error )
510
+ say(@question.responses[error]) unless error.nil?
511
+ if @question.responses[:ask_on_error] == :question
512
+ say(@question)
513
+ elsif @question.responses[:ask_on_error]
514
+ say(@question.responses[:ask_on_error])
515
+ end
516
+ end
517
+
518
+ #
519
+ # Collects an Array/Hash full of answers as described in
520
+ # HighLine::Question.gather().
521
+ #
522
+ # Raises EOFError if input is exhausted.
523
+ #
524
+ def gather( )
525
+ @gather = @question.gather
526
+ @answers = [ ]
527
+ original_question = @question
528
+
529
+ @question.gather = false
530
+
531
+ case @gather
532
+ when Integer
533
+ @answers << ask(@question)
534
+ @gather -= 1
535
+
536
+ original_question.question = ""
537
+ until @gather.zero?
538
+ @question = original_question
539
+ @answers << ask(@question)
540
+ @gather -= 1
541
+ end
542
+ when String, Regexp
543
+ @answers << ask(@question)
544
+
545
+ original_question.question = ""
546
+ until (@gather.is_a?(String) and @answers.last.to_s == @gather) or
547
+ (@gather.is_a?(Regexp) and @answers.last.to_s =~ @gather)
548
+ @question = original_question
549
+ @answers << ask(@question)
550
+ end
551
+
552
+ @answers.pop
553
+ when Hash
554
+ @answers = { }
555
+ @gather.keys.sort.each do |key|
556
+ @question = original_question
557
+ @key = key
558
+ @answers[key] = ask(@question)
559
+ end
560
+ end
561
+
562
+ @answers
563
+ end
564
+
565
+ #
566
+ # Read a line of input from the input stream and process whitespace as
567
+ # requested by the Question object.
568
+ #
569
+ # If Question's _readline_ property is set, that library will be used to
570
+ # fetch input. *WARNING*: This ignores the currently set input stream.
571
+ #
572
+ # Raises EOFError if input is exhausted.
573
+ #
574
+ def get_line( )
575
+ if @question.readline
576
+ require "readline" # load only if needed
577
+
578
+ # capture say()'s work in a String to feed to readline()
579
+ old_output = @output
580
+ @output = StringIO.new
581
+ say(@question)
582
+ question = @output.string
583
+ @output = old_output
584
+
585
+ # prep auto-completion
586
+ # I found this version in plugin
587
+ # The previous version wasn`t warking properly, readline wasn`t
588
+ # showing possible answers if you push tab 2 times
589
+ Readline.completion_proc = lambda do |string|
590
+ @question.selection.grep(/\A#{Regexp.escape(string)}/)
591
+ end
592
+
593
+
594
+
595
+
596
+ # work-around ugly readline() warnings
597
+ old_verbose = $VERBOSE
598
+ $VERBOSE = nil
599
+ answer = @question.change_case(
600
+ @question.remove_whitespace(
601
+ Readline.readline(question, true) ) )
602
+ $VERBOSE = old_verbose
603
+
604
+ answer
605
+ else
606
+ raise EOFError, "The input stream is exhausted." if @@track_eof and
607
+ @input.eof?
608
+
609
+ @question.change_case(@question.remove_whitespace(@input.gets))
610
+ end
611
+ end
612
+
613
+ #
614
+ # Return a line or character of input, as requested for this question.
615
+ # Character input will be returned as a single character String,
616
+ # not an Integer.
617
+ #
618
+ # This question's _first_answer_ will be returned instead of input, if set.
619
+ #
620
+ # Raises EOFError if input is exhausted.
621
+ #
622
+ def get_response( )
623
+ return @question.first_answer if @question.first_answer?
624
+
625
+ if @question.character.nil?
626
+ if @question.echo == true and @question.limit.nil?
627
+ get_line
628
+ else
629
+ raw_no_echo_mode if stty = CHARACTER_MODE == "stty"
630
+
631
+ line = ""
632
+ backspace_limit = 0
633
+ begin
634
+
635
+ while character = (stty ? @input.getc : get_character(@input))
636
+ # honor backspace and delete
637
+ if character == 127 or character == 8
638
+ line.slice!(-1, 1)
639
+ backspace_limit -= 1
640
+ else
641
+ line << character.chr
642
+ backspace_limit = line.size
643
+ end
644
+ # looking for carriage return (decimal 13) or
645
+ # newline (decimal 10) in raw input
646
+ break if character == 13 or character == 10 or
647
+ (@question.limit and line.size == @question.limit)
648
+ if @question.echo != false
649
+ if character == 127 or character == 8
650
+ # only backspace if we have characters on the line to
651
+ # eliminate, otherwise we'll tromp over the prompt
652
+ if backspace_limit >= 0 then
653
+ @output.print("\b#{ERASE_CHAR}")
654
+ else
655
+ # do nothing
656
+ end
657
+ else
658
+ @output.print(@question.echo)
659
+ end
660
+ @output.flush
661
+ end
662
+ end
663
+ ensure
664
+ restore_mode if stty
665
+ end
666
+ if @question.overwrite
667
+ @output.print("\r#{ERASE_LINE}")
668
+ @output.flush
669
+ else
670
+ say("\n")
671
+ end
672
+
673
+ @question.change_case(@question.remove_whitespace(line))
674
+ end
675
+ elsif @question.character == :getc
676
+ @question.change_case(@input.getc.chr)
677
+ else
678
+ response = get_character(@input).chr
679
+ if @question.overwrite
680
+ @output.print("\r#{ERASE_LINE}")
681
+ @output.flush
682
+ else
683
+ echo = if @question.echo == true
684
+ response
685
+ elsif @question.echo != false
686
+ @question.echo
687
+ else
688
+ ""
689
+ end
690
+ say("#{echo}\n")
691
+ end
692
+ @question.change_case(response)
693
+ end
694
+ end
695
+
696
+ #
697
+ # Page print a series of at most _page_at_ lines for _output_. After each
698
+ # page is printed, HighLine will pause until the user presses enter/return
699
+ # then display the next page of data.
700
+ #
701
+ # Note that the final page of _output_ is *not* printed, but returned
702
+ # instead. This is to support any special handling for the final sequence.
703
+ #
704
+ def page_print( output )
705
+ lines = output.scan(/[^\n]*\n?/)
706
+ while lines.size > @page_at
707
+ @output.puts lines.slice!(0...@page_at).join
708
+ @output.puts
709
+ # Return last line if user wants to abort paging
710
+ return (["...\n"] + lines.slice(-2,1)).join unless continue_paging?
711
+ end
712
+ return lines.join
713
+ end
714
+
715
+ #
716
+ # Ask user if they wish to continue paging output. Allows them to type "q" to
717
+ # cancel the paging process.
718
+ #
719
+ def continue_paging?
720
+ command = HighLine.new(@input, @output).ask(
721
+ "-- press enter/return to continue or q to stop -- "
722
+ ) { |q| q.character = true }
723
+ command !~ /\A[qQ]\Z/ # Only continue paging if Q was not hit.
724
+ end
725
+
726
+ #
727
+ # Wrap a sequence of _lines_ at _wrap_at_ characters per line. Existing
728
+ # newlines will not be affected by this process, but additional newlines
729
+ # may be added.
730
+ #
731
+ def wrap( lines )
732
+ wrapped = [ ]
733
+ lines.each do |line|
734
+ while line =~ /([^\n]{#{@wrap_at + 1},})/
735
+ search = $1.dup
736
+ replace = $1.dup
737
+ if index = replace.rindex(" ", @wrap_at)
738
+ replace[index, 1] = "\n"
739
+ replace.sub!(/\n[ \t]+/, "\n")
740
+ line.sub!(search, replace)
741
+ else
742
+ line[@wrap_at, 0] = "\n"
743
+ end
744
+ end
745
+ wrapped << line
746
+ end
747
+ return wrapped.join
748
+ end
749
+
750
+ #
751
+ # Returns the length of the passed +string_with_escapes+, minus and color
752
+ # sequence escapes.
753
+ #
754
+ def actual_length( string_with_escapes )
755
+ string_with_escapes.gsub(/\e\[\d{1,2}m/, "").length
756
+ end
757
+ end