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