highline 1.6.1 → 1.6.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +2 -0
- data/AUTHORS +3 -0
- data/CHANGELOG +76 -0
- data/COPYING +340 -0
- data/Rakefile +9 -41
- data/doc/.cvsignore +1 -0
- data/examples/ansi_colors.rb +1 -1
- data/examples/asking_for_arrays.rb +1 -1
- data/examples/basic_usage.rb +1 -1
- data/examples/menus.rb +1 -1
- data/examples/overwrite.rb +1 -1
- data/examples/page_and_wrap.rb +1 -1
- data/examples/password.rb +1 -1
- data/examples/repeat_entry.rb +21 -0
- data/examples/trapping_eof.rb +1 -1
- data/examples/using_readline.rb +1 -1
- data/highline.gemspec +36 -0
- data/lib/highline/color_scheme.rb +20 -4
- data/lib/highline/import.rb +0 -2
- data/lib/highline/menu.rb +7 -6
- data/lib/highline/question.rb +17 -7
- data/lib/highline/simulate.rb +48 -0
- data/lib/highline/string_extensions.rb +131 -0
- data/lib/highline/style.rb +181 -0
- data/lib/highline/system_extensions.rb +106 -100
- data/lib/highline.rb +406 -202
- data/site/.cvsignore +1 -0
- data/site/highline.css +65 -0
- data/site/images/logo.png +0 -0
- data/site/index.html +58 -0
- data/test/string_methods.rb +32 -0
- data/test/tc_color_scheme.rb +42 -2
- data/test/tc_highline.rb +215 -11
- data/test/tc_import.rb +0 -2
- data/test/tc_menu.rb +0 -2
- data/test/tc_string_extension.rb +20 -0
- data/test/tc_string_highline.rb +38 -0
- data/test/tc_style.rb +567 -0
- data/test/ts_all.rb +3 -2
- metadata +62 -52
- /data/{README → README.rdoc} +0 -0
data/lib/highline.rb
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
#!/usr/local/bin/ruby -w
|
|
2
|
-
|
|
3
1
|
# highline.rb
|
|
4
2
|
#
|
|
5
3
|
# Created by James Edward Gray II on 2005-04-26.
|
|
@@ -17,6 +15,7 @@ require "highline/system_extensions"
|
|
|
17
15
|
require "highline/question"
|
|
18
16
|
require "highline/menu"
|
|
19
17
|
require "highline/color_scheme"
|
|
18
|
+
require "highline/style"
|
|
20
19
|
|
|
21
20
|
#
|
|
22
21
|
# A HighLine object is a "high-level line oriented" shell over an input and an
|
|
@@ -29,7 +28,7 @@ require "highline/color_scheme"
|
|
|
29
28
|
#
|
|
30
29
|
class HighLine
|
|
31
30
|
# The version of the installed library.
|
|
32
|
-
VERSION = "1.6.
|
|
31
|
+
VERSION = "1.6.14".freeze
|
|
33
32
|
|
|
34
33
|
# An internal HighLine error. User code does not need to trap this.
|
|
35
34
|
class QuestionError < StandardError
|
|
@@ -49,6 +48,13 @@ class HighLine
|
|
|
49
48
|
@@use_color
|
|
50
49
|
end
|
|
51
50
|
|
|
51
|
+
# For checking if the current version of HighLine supports RGB colors
|
|
52
|
+
# Usage: HighLine.supports_rgb_color? rescue false # rescue for compatibility with older versions
|
|
53
|
+
# Note: color usage also depends on HighLine.use_color being set
|
|
54
|
+
def self.supports_rgb_color?
|
|
55
|
+
true
|
|
56
|
+
end
|
|
57
|
+
|
|
52
58
|
# The setting used to disable EOF tracking.
|
|
53
59
|
@@track_eof = true
|
|
54
60
|
|
|
@@ -83,62 +89,85 @@ class HighLine
|
|
|
83
89
|
#
|
|
84
90
|
# Embed in a String to clear all previous ANSI sequences. This *MUST* be
|
|
85
91
|
# done before the program exits!
|
|
86
|
-
#
|
|
87
|
-
|
|
88
|
-
#
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
#
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
#
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
#
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
#
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
92
|
+
#
|
|
93
|
+
|
|
94
|
+
ERASE_LINE_STYLE = Style.new(:name=>:erase_line, :builtin=>true, :code=>"\e[K") # Erase the current line of terminal output
|
|
95
|
+
ERASE_CHAR_STYLE = Style.new(:name=>:erase_char, :builtin=>true, :code=>"\e[P") # Erase the character under the cursor.
|
|
96
|
+
CLEAR_STYLE = Style.new(:name=>:clear, :builtin=>true, :code=>"\e[0m") # Clear color settings
|
|
97
|
+
RESET_STYLE = Style.new(:name=>:reset, :builtin=>true, :code=>"\e[0m") # Alias for CLEAR.
|
|
98
|
+
BOLD_STYLE = Style.new(:name=>:bold, :builtin=>true, :code=>"\e[1m") # Bold; Note: bold + a color works as you'd expect,
|
|
99
|
+
# for example bold black. Bold without a color displays
|
|
100
|
+
# the system-defined bold color (e.g. red on Mac iTerm)
|
|
101
|
+
DARK_STYLE = Style.new(:name=>:dark, :builtin=>true, :code=>"\e[2m") # Dark; support uncommon
|
|
102
|
+
UNDERLINE_STYLE = Style.new(:name=>:underline, :builtin=>true, :code=>"\e[4m") # Underline
|
|
103
|
+
UNDERSCORE_STYLE = Style.new(:name=>:underscore, :builtin=>true, :code=>"\e[4m") # Alias for UNDERLINE
|
|
104
|
+
BLINK_STYLE = Style.new(:name=>:blink, :builtin=>true, :code=>"\e[5m") # Blink; support uncommon
|
|
105
|
+
REVERSE_STYLE = Style.new(:name=>:reverse, :builtin=>true, :code=>"\e[7m") # Reverse foreground and background
|
|
106
|
+
CONCEALED_STYLE = Style.new(:name=>:concealed, :builtin=>true, :code=>"\e[8m") # Concealed; support uncommon
|
|
107
|
+
|
|
108
|
+
STYLES = %w{CLEAR RESET BOLD DARK UNDERLINE UNDERSCORE BLINK REVERSE CONCEALED}
|
|
109
|
+
|
|
110
|
+
# These RGB colors are approximate; see http://en.wikipedia.org/wiki/ANSI_escape_code
|
|
111
|
+
BLACK_STYLE = Style.new(:name=>:black, :builtin=>true, :code=>"\e[30m", :rgb=>[ 0, 0, 0])
|
|
112
|
+
RED_STYLE = Style.new(:name=>:red, :builtin=>true, :code=>"\e[31m", :rgb=>[128, 0, 0])
|
|
113
|
+
GREEN_STYLE = Style.new(:name=>:green, :builtin=>true, :code=>"\e[32m", :rgb=>[ 0,128, 0])
|
|
114
|
+
BLUE_STYLE = Style.new(:name=>:blue, :builtin=>true, :code=>"\e[34m", :rgb=>[ 0, 0,128])
|
|
115
|
+
YELLOW_STYLE = Style.new(:name=>:yellow, :builtin=>true, :code=>"\e[33m", :rgb=>[128,128, 0])
|
|
116
|
+
MAGENTA_STYLE = Style.new(:name=>:magenta, :builtin=>true, :code=>"\e[35m", :rgb=>[128, 0,128])
|
|
117
|
+
CYAN_STYLE = Style.new(:name=>:cyan, :builtin=>true, :code=>"\e[36m", :rgb=>[ 0,128,128])
|
|
118
|
+
# On Mac OSX Terminal, white is actually gray
|
|
119
|
+
WHITE_STYLE = Style.new(:name=>:white, :builtin=>true, :code=>"\e[37m", :rgb=>[192,192,192])
|
|
120
|
+
# Alias for WHITE, since WHITE is actually a light gray on Macs
|
|
121
|
+
GRAY_STYLE = Style.new(:name=>:gray, :builtin=>true, :code=>"\e[37m", :rgb=>[192,192,192])
|
|
122
|
+
# On Mac OSX Terminal, this is black foreground, or bright white background.
|
|
123
|
+
# Also used as base for RGB colors, if available
|
|
124
|
+
NONE_STYLE = Style.new(:name=>:none, :builtin=>true, :code=>"\e[38m", :rgb=>[ 0, 0, 0])
|
|
125
|
+
|
|
126
|
+
BASIC_COLORS = %w{BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE GRAY NONE}
|
|
127
|
+
|
|
128
|
+
colors = BASIC_COLORS.dup
|
|
129
|
+
BASIC_COLORS.each do |color|
|
|
130
|
+
bright_color = "BRIGHT_#{color}"
|
|
131
|
+
colors << bright_color
|
|
132
|
+
const_set bright_color+'_STYLE', const_get(color + '_STYLE').bright
|
|
133
|
+
end
|
|
134
|
+
COLORS = colors
|
|
135
|
+
|
|
136
|
+
colors.each do |color|
|
|
137
|
+
const_set color, const_get("#{color}_STYLE").code
|
|
138
|
+
const_set "ON_#{color}_STYLE", const_get("#{color}_STYLE").on
|
|
139
|
+
const_set "ON_#{color}", const_get("ON_#{color}_STYLE").code
|
|
140
|
+
end
|
|
141
|
+
ON_NONE_STYLE.rgb = [255,255,255] # Override; white background
|
|
142
|
+
|
|
143
|
+
STYLES.each do |style|
|
|
144
|
+
const_set style, const_get("#{style}_STYLE").code
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# For RGB colors:
|
|
148
|
+
def self.const_missing(name)
|
|
149
|
+
if name.to_s =~ /^(ON_)?(RGB_)([A-F0-9]{6})(_STYLE)?$/ # RGB color
|
|
150
|
+
on = $1
|
|
151
|
+
suffix = $4
|
|
152
|
+
if suffix
|
|
153
|
+
code_name = $1.to_s + $2 + $3
|
|
154
|
+
else
|
|
155
|
+
code_name = name.to_s
|
|
156
|
+
end
|
|
157
|
+
style_name = code_name + '_STYLE'
|
|
158
|
+
style = Style.rgb($3)
|
|
159
|
+
style = style.on if on
|
|
160
|
+
const_set(style_name, style)
|
|
161
|
+
const_set(code_name, style.code)
|
|
162
|
+
if suffix
|
|
163
|
+
style
|
|
164
|
+
else
|
|
165
|
+
style.code
|
|
166
|
+
end
|
|
167
|
+
else
|
|
168
|
+
raise NameError, "Bad color or uninitialized constant #{name}"
|
|
169
|
+
end
|
|
170
|
+
end
|
|
142
171
|
|
|
143
172
|
#
|
|
144
173
|
# Create an instance of HighLine, connected to the streams _input_
|
|
@@ -146,6 +175,7 @@ class HighLine
|
|
|
146
175
|
#
|
|
147
176
|
def initialize( input = $stdin, output = $stdout,
|
|
148
177
|
wrap_at = nil, page_at = nil )
|
|
178
|
+
super()
|
|
149
179
|
@input = input
|
|
150
180
|
@output = output
|
|
151
181
|
|
|
@@ -211,7 +241,9 @@ class HighLine
|
|
|
211
241
|
# readline() needs to handle it's own output, but readline only supports
|
|
212
242
|
# full line reading. Therefore if @question.echo is anything but true,
|
|
213
243
|
# the prompt will not be issued. And we have to account for that now.
|
|
214
|
-
|
|
244
|
+
# Also, JRuby-1.7's ConsoleReader.readLine() needs to be passed the prompt
|
|
245
|
+
# to handle line editing properly.
|
|
246
|
+
say(@question) unless ((JRUBY or @question.readline) and @question.echo == true)
|
|
215
247
|
begin
|
|
216
248
|
@answer = @question.answer_or_default(get_response)
|
|
217
249
|
unless @question.valid_answer?(@answer)
|
|
@@ -328,19 +360,34 @@ class HighLine
|
|
|
328
360
|
# This method returns the original _string_ unchanged if HighLine::use_color?
|
|
329
361
|
# is +false+.
|
|
330
362
|
#
|
|
331
|
-
def color( string, *colors )
|
|
332
|
-
return string unless self.
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
363
|
+
def self.color( string, *colors )
|
|
364
|
+
return string unless self.use_color?
|
|
365
|
+
Style(*colors).color(string)
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
# In case you just want the color code, without the embedding and the CLEAR
|
|
369
|
+
def self.color_code(*colors)
|
|
370
|
+
Style(*colors).code
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
# Works as an instance method, same as the class method
|
|
374
|
+
def color_code(*colors)
|
|
375
|
+
self.class.color_code(*colors)
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
# Works as an instance method, same as the class method
|
|
379
|
+
def color(*args)
|
|
380
|
+
self.class.color(*args)
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
# Remove color codes from a string
|
|
384
|
+
def self.uncolor(string)
|
|
385
|
+
Style.uncolor(string)
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
# Works as an instance method, same as the class method
|
|
389
|
+
def uncolor(string)
|
|
390
|
+
self.class.uncolor(string)
|
|
344
391
|
end
|
|
345
392
|
|
|
346
393
|
#
|
|
@@ -352,20 +399,25 @@ class HighLine
|
|
|
352
399
|
# to list. A specified _mode_ controls how that list is formed and _option_
|
|
353
400
|
# has different effects, depending on the _mode_. Recognized modes are:
|
|
354
401
|
#
|
|
355
|
-
# <tt>:columns_across</tt>::
|
|
356
|
-
#
|
|
357
|
-
#
|
|
358
|
-
#
|
|
359
|
-
#
|
|
360
|
-
#
|
|
361
|
-
#
|
|
362
|
-
#
|
|
363
|
-
#
|
|
364
|
-
#
|
|
365
|
-
#
|
|
366
|
-
#
|
|
367
|
-
#
|
|
368
|
-
#
|
|
402
|
+
# <tt>:columns_across</tt>:: _items_ will be placed in columns,
|
|
403
|
+
# flowing from left to right. If given,
|
|
404
|
+
# _option_ is the number of columns to be
|
|
405
|
+
# used. When absent, columns will be
|
|
406
|
+
# determined based on _wrap_at_ or a
|
|
407
|
+
# default of 80 characters.
|
|
408
|
+
# <tt>:columns_down</tt>:: Identical to <tt>:columns_across</tt>,
|
|
409
|
+
# save flow goes down.
|
|
410
|
+
# <tt>:uneven_columns_across</tt>:: Like <tt>:columns_across</tt> but each
|
|
411
|
+
# column is sized independently.
|
|
412
|
+
# <tt>:uneven_columns_down</tt>:: Like <tt>:columns_down</tt> but each
|
|
413
|
+
# column is sized independently.
|
|
414
|
+
# <tt>:inline</tt>:: All _items_ are placed on a single line.
|
|
415
|
+
# The last two _items_ are separated by
|
|
416
|
+
# _option_ or a default of " or ". All
|
|
417
|
+
# other _items_ are separated by ", ".
|
|
418
|
+
# <tt>:rows</tt>:: The default mode. Each of the _items_ is
|
|
419
|
+
# placed on it's own line. The _option_
|
|
420
|
+
# parameter is ignored in this mode.
|
|
369
421
|
#
|
|
370
422
|
# Each member of the _items_ Array is passed through ERb and thus can contain
|
|
371
423
|
# their own expansions. Color escape expansions do not contribute to the
|
|
@@ -376,58 +428,160 @@ class HighLine
|
|
|
376
428
|
ERB.new(item, nil, "%").result(binding)
|
|
377
429
|
end
|
|
378
430
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
431
|
+
if items.empty?
|
|
432
|
+
""
|
|
433
|
+
else
|
|
434
|
+
case mode
|
|
435
|
+
when :inline
|
|
436
|
+
option = " or " if option.nil?
|
|
437
|
+
|
|
438
|
+
if items.size == 1
|
|
439
|
+
items.first
|
|
440
|
+
else
|
|
441
|
+
items[0..-2].join(", ") + "#{option}#{items.last}"
|
|
442
|
+
end
|
|
443
|
+
when :columns_across, :columns_down
|
|
444
|
+
max_length = actual_length(
|
|
445
|
+
items.max { |a, b| actual_length(a) <=> actual_length(b) }
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
if option.nil?
|
|
449
|
+
limit = @wrap_at || 80
|
|
450
|
+
option = (limit + 2) / (max_length + 2)
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
items = items.map do |item|
|
|
454
|
+
pad = max_length + (item.to_s.length - actual_length(item))
|
|
455
|
+
"%-#{pad}s" % item
|
|
456
|
+
end
|
|
457
|
+
row_count = (items.size / option.to_f).ceil
|
|
382
458
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
when 2
|
|
389
|
-
"#{items.first}#{option}#{items.last}"
|
|
390
|
-
else
|
|
391
|
-
items[0..-2].join(", ") + "#{option}#{items.last}"
|
|
392
|
-
end
|
|
393
|
-
when :columns_across, :columns_down
|
|
394
|
-
max_length = actual_length(
|
|
395
|
-
items.max { |a, b| actual_length(a) <=> actual_length(b) }
|
|
396
|
-
)
|
|
397
|
-
|
|
398
|
-
if option.nil?
|
|
399
|
-
limit = @wrap_at || 80
|
|
400
|
-
option = (limit + 2) / (max_length + 2)
|
|
401
|
-
end
|
|
459
|
+
if mode == :columns_across
|
|
460
|
+
rows = Array.new(row_count) { Array.new }
|
|
461
|
+
items.each_with_index do |item, index|
|
|
462
|
+
rows[index / option] << item
|
|
463
|
+
end
|
|
402
464
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
465
|
+
rows.map { |row| row.join(" ") + "\n" }.join
|
|
466
|
+
else
|
|
467
|
+
columns = Array.new(option) { Array.new }
|
|
468
|
+
items.each_with_index do |item, index|
|
|
469
|
+
columns[index / row_count] << item
|
|
470
|
+
end
|
|
408
471
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
472
|
+
list = ""
|
|
473
|
+
columns.first.size.times do |index|
|
|
474
|
+
list << columns.map { |column| column[index] }.
|
|
475
|
+
compact.join(" ") + "\n"
|
|
476
|
+
end
|
|
477
|
+
list
|
|
413
478
|
end
|
|
479
|
+
when :uneven_columns_across
|
|
480
|
+
if option.nil?
|
|
481
|
+
limit = @wrap_at || 80
|
|
482
|
+
items.size.downto(1) do |column_count|
|
|
483
|
+
row_count = (items.size / column_count.to_f).ceil
|
|
484
|
+
rows = Array.new(row_count) { Array.new }
|
|
485
|
+
items.each_with_index do |item, index|
|
|
486
|
+
rows[index / column_count] << item
|
|
487
|
+
end
|
|
414
488
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
489
|
+
widths = Array.new(column_count, 0)
|
|
490
|
+
rows.each do |row|
|
|
491
|
+
row.each_with_index do |field, column|
|
|
492
|
+
size = actual_length(field)
|
|
493
|
+
widths[column] = size if size > widths[column]
|
|
494
|
+
end
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
if column_count == 1 or
|
|
498
|
+
widths.inject(0) { |sum, n| sum + n + 2 } <= limit + 2
|
|
499
|
+
return rows.map { |row|
|
|
500
|
+
row.zip(widths).map { |field, i|
|
|
501
|
+
"%-#{i + (field.to_s.length - actual_length(field))}s" % field
|
|
502
|
+
}.join(" ") + "\n"
|
|
503
|
+
}.join
|
|
504
|
+
end
|
|
505
|
+
end
|
|
506
|
+
else
|
|
507
|
+
row_count = (items.size / option.to_f).ceil
|
|
508
|
+
rows = Array.new(row_count) { Array.new }
|
|
509
|
+
items.each_with_index do |item, index|
|
|
510
|
+
rows[index / option] << item
|
|
511
|
+
end
|
|
512
|
+
|
|
513
|
+
widths = Array.new(option, 0)
|
|
514
|
+
rows.each do |row|
|
|
515
|
+
row.each_with_index do |field, column|
|
|
516
|
+
size = actual_length(field)
|
|
517
|
+
widths[column] = size if size > widths[column]
|
|
518
|
+
end
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
return rows.map { |row|
|
|
522
|
+
row.zip(widths).map { |field, i|
|
|
523
|
+
"%-#{i + (field.to_s.length - actual_length(field))}s" % field
|
|
524
|
+
}.join(" ") + "\n"
|
|
525
|
+
}.join
|
|
420
526
|
end
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
527
|
+
when :uneven_columns_down
|
|
528
|
+
if option.nil?
|
|
529
|
+
limit = @wrap_at || 80
|
|
530
|
+
items.size.downto(1) do |column_count|
|
|
531
|
+
row_count = (items.size / column_count.to_f).ceil
|
|
532
|
+
columns = Array.new(column_count) { Array.new }
|
|
533
|
+
items.each_with_index do |item, index|
|
|
534
|
+
columns[index / row_count] << item
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
widths = Array.new(column_count, 0)
|
|
538
|
+
columns.each_with_index do |column, i|
|
|
539
|
+
column.each do |field|
|
|
540
|
+
size = actual_length(field)
|
|
541
|
+
widths[i] = size if size > widths[i]
|
|
542
|
+
end
|
|
543
|
+
end
|
|
544
|
+
|
|
545
|
+
if column_count == 1 or
|
|
546
|
+
widths.inject(0) { |sum, n| sum + n + 2 } <= limit + 2
|
|
547
|
+
list = ""
|
|
548
|
+
columns.first.size.times do |index|
|
|
549
|
+
list << columns.zip(widths).map { |column, width|
|
|
550
|
+
field = column[index]
|
|
551
|
+
"%-#{width + (field.to_s.length - actual_length(field))}s" %
|
|
552
|
+
field
|
|
553
|
+
}.compact.join(" ").strip + "\n"
|
|
554
|
+
end
|
|
555
|
+
return list
|
|
556
|
+
end
|
|
557
|
+
end
|
|
558
|
+
else
|
|
559
|
+
row_count = (items.size / option.to_f).ceil
|
|
560
|
+
columns = Array.new(option) { Array.new }
|
|
561
|
+
items.each_with_index do |item, index|
|
|
562
|
+
columns[index / row_count] << item
|
|
563
|
+
end
|
|
564
|
+
|
|
565
|
+
widths = Array.new(option, 0)
|
|
566
|
+
columns.each_with_index do |column, i|
|
|
567
|
+
column.each do |field|
|
|
568
|
+
size = actual_length(field)
|
|
569
|
+
widths[i] = size if size > widths[i]
|
|
570
|
+
end
|
|
571
|
+
end
|
|
572
|
+
|
|
573
|
+
list = ""
|
|
574
|
+
columns.first.size.times do |index|
|
|
575
|
+
list << columns.zip(widths).map { |column, width|
|
|
576
|
+
field = column[index]
|
|
577
|
+
"%-#{width + (field.to_s.length - actual_length(field))}s" % field
|
|
578
|
+
}.compact.join(" ").strip + "\n"
|
|
579
|
+
end
|
|
580
|
+
return list
|
|
426
581
|
end
|
|
427
|
-
|
|
582
|
+
else
|
|
583
|
+
items.map { |i| "#{i}\n" }.join
|
|
428
584
|
end
|
|
429
|
-
else
|
|
430
|
-
items.map { |i| "#{i}\n" }.join
|
|
431
585
|
end
|
|
432
586
|
end
|
|
433
587
|
|
|
@@ -523,44 +677,68 @@ class HighLine
|
|
|
523
677
|
# Raises EOFError if input is exhausted.
|
|
524
678
|
#
|
|
525
679
|
def gather( )
|
|
526
|
-
@gather = @question.gather
|
|
527
|
-
@answers = [ ]
|
|
528
680
|
original_question = @question
|
|
529
|
-
|
|
681
|
+
original_question_string = @question.question
|
|
682
|
+
original_gather = @question.gather
|
|
683
|
+
|
|
684
|
+
verify_match = @question.verify_match
|
|
530
685
|
@question.gather = false
|
|
531
|
-
|
|
532
|
-
case @gather
|
|
533
|
-
when Integer
|
|
534
|
-
@answers << ask(@question)
|
|
535
|
-
@gather -= 1
|
|
536
686
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
687
|
+
begin # when verify_match is set this loop will repeat until unique_answers == 1
|
|
688
|
+
@answers = [ ]
|
|
689
|
+
@gather = original_gather
|
|
690
|
+
original_question.question = original_question_string
|
|
691
|
+
|
|
692
|
+
case @gather
|
|
693
|
+
when Integer
|
|
694
|
+
@answers << ask(@question)
|
|
695
|
+
@gather -= 1
|
|
696
|
+
|
|
697
|
+
original_question.question = ""
|
|
698
|
+
until @gather.zero?
|
|
699
|
+
@question = original_question
|
|
700
|
+
@answers << ask(@question)
|
|
701
|
+
@gather -= 1
|
|
702
|
+
end
|
|
703
|
+
when ::String, Regexp
|
|
704
|
+
@answers << ask(@question)
|
|
545
705
|
|
|
546
|
-
|
|
547
|
-
|
|
706
|
+
original_question.question = ""
|
|
707
|
+
until (@gather.is_a?(::String) and @answers.last.to_s == @gather) or
|
|
548
708
|
(@gather.is_a?(Regexp) and @answers.last.to_s =~ @gather)
|
|
549
|
-
|
|
550
|
-
|
|
709
|
+
@question = original_question
|
|
710
|
+
@answers << ask(@question)
|
|
711
|
+
end
|
|
712
|
+
|
|
713
|
+
@answers.pop
|
|
714
|
+
when Hash
|
|
715
|
+
@answers = { }
|
|
716
|
+
@gather.keys.sort.each do |key|
|
|
717
|
+
@question = original_question
|
|
718
|
+
@key = key
|
|
719
|
+
@answers[key] = ask(@question)
|
|
720
|
+
end
|
|
551
721
|
end
|
|
552
|
-
|
|
553
|
-
@answers.
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
@key = key
|
|
559
|
-
@answers[key] = ask(@question)
|
|
722
|
+
|
|
723
|
+
if verify_match && (unique_answers(@answers).size > 1)
|
|
724
|
+
@question = original_question
|
|
725
|
+
explain_error(:mismatch)
|
|
726
|
+
else
|
|
727
|
+
verify_match = false
|
|
560
728
|
end
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
729
|
+
|
|
730
|
+
end while verify_match
|
|
731
|
+
|
|
732
|
+
original_question.verify_match ? @answer : @answers
|
|
733
|
+
end
|
|
734
|
+
|
|
735
|
+
#
|
|
736
|
+
# A helper method used by HighLine::Question.verify_match
|
|
737
|
+
# for finding whether a list of answers match or differ
|
|
738
|
+
# from each other.
|
|
739
|
+
#
|
|
740
|
+
def unique_answers(list = @answers)
|
|
741
|
+
(list.respond_to?(:values) ? list.values : list).uniq
|
|
564
742
|
end
|
|
565
743
|
|
|
566
744
|
#
|
|
@@ -591,20 +769,32 @@ class HighLine
|
|
|
591
769
|
# work-around ugly readline() warnings
|
|
592
770
|
old_verbose = $VERBOSE
|
|
593
771
|
$VERBOSE = nil
|
|
772
|
+
raw_answer = Readline.readline(question, true)
|
|
773
|
+
if raw_answer.nil?
|
|
774
|
+
if @@track_eof
|
|
775
|
+
raise EOFError, "The input stream is exhausted."
|
|
776
|
+
else
|
|
777
|
+
raw_answer = String.new # Never return nil
|
|
778
|
+
end
|
|
779
|
+
end
|
|
594
780
|
answer = @question.change_case(
|
|
595
|
-
@question.remove_whitespace(
|
|
596
|
-
Readline.readline(question, true) ) )
|
|
781
|
+
@question.remove_whitespace(raw_answer))
|
|
597
782
|
$VERBOSE = old_verbose
|
|
598
783
|
|
|
599
784
|
answer
|
|
600
785
|
else
|
|
601
|
-
|
|
602
|
-
|
|
786
|
+
if JRUBY
|
|
787
|
+
raw_answer = @java_console.readLine(@question.question, nil)
|
|
788
|
+
else
|
|
789
|
+
raise EOFError, "The input stream is exhausted." if @@track_eof and
|
|
790
|
+
@input.eof?
|
|
791
|
+
raw_answer = @input.gets
|
|
792
|
+
end
|
|
603
793
|
|
|
604
|
-
@question.change_case(@question.remove_whitespace(
|
|
794
|
+
@question.change_case(@question.remove_whitespace(raw_answer))
|
|
605
795
|
end
|
|
606
796
|
end
|
|
607
|
-
|
|
797
|
+
|
|
608
798
|
#
|
|
609
799
|
# Return a line or character of input, as requested for this question.
|
|
610
800
|
# Character input will be returned as a single character String,
|
|
@@ -616,18 +806,20 @@ class HighLine
|
|
|
616
806
|
#
|
|
617
807
|
def get_response( )
|
|
618
808
|
return @question.first_answer if @question.first_answer?
|
|
619
|
-
|
|
809
|
+
|
|
810
|
+
stty = (CHARACTER_MODE == "stty")
|
|
811
|
+
|
|
620
812
|
if @question.character.nil?
|
|
621
813
|
if @question.echo == true and @question.limit.nil?
|
|
622
814
|
get_line
|
|
623
815
|
else
|
|
624
|
-
raw_no_echo_mode
|
|
625
|
-
|
|
816
|
+
raw_no_echo_mode
|
|
817
|
+
|
|
626
818
|
line = ""
|
|
627
819
|
backspace_limit = 0
|
|
628
820
|
begin
|
|
629
821
|
|
|
630
|
-
while character =
|
|
822
|
+
while character = get_character(@input)
|
|
631
823
|
# honor backspace and delete
|
|
632
824
|
if character == 127 or character == 8
|
|
633
825
|
line.slice!(-1, 1)
|
|
@@ -638,17 +830,16 @@ class HighLine
|
|
|
638
830
|
end
|
|
639
831
|
# looking for carriage return (decimal 13) or
|
|
640
832
|
# newline (decimal 10) in raw input
|
|
641
|
-
break if character == 13 or character == 10
|
|
642
|
-
(@question.limit and line.size == @question.limit)
|
|
833
|
+
break if character == 13 or character == 10
|
|
643
834
|
if @question.echo != false
|
|
644
|
-
if character == 127 or character == 8
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
835
|
+
if character == 127 or character == 8
|
|
836
|
+
# only backspace if we have characters on the line to
|
|
837
|
+
# eliminate, otherwise we'll tromp over the prompt
|
|
838
|
+
if backspace_limit >= 0 then
|
|
839
|
+
@output.print("\b#{HighLine.Style(:erase_char).code}")
|
|
840
|
+
else
|
|
841
|
+
# do nothing
|
|
842
|
+
end
|
|
652
843
|
else
|
|
653
844
|
if @question.echo == true
|
|
654
845
|
@output.print(character.chr)
|
|
@@ -658,35 +849,43 @@ class HighLine
|
|
|
658
849
|
end
|
|
659
850
|
@output.flush
|
|
660
851
|
end
|
|
852
|
+
break if @question.limit and line.size == @question.limit
|
|
661
853
|
end
|
|
662
854
|
ensure
|
|
663
|
-
restore_mode
|
|
855
|
+
restore_mode
|
|
664
856
|
end
|
|
665
857
|
if @question.overwrite
|
|
666
|
-
@output.print("\r#{
|
|
858
|
+
@output.print("\r#{HighLine.Style(:erase_line).code}")
|
|
667
859
|
@output.flush
|
|
668
860
|
else
|
|
669
861
|
say("\n")
|
|
670
862
|
end
|
|
671
|
-
|
|
863
|
+
|
|
672
864
|
@question.change_case(@question.remove_whitespace(line))
|
|
673
865
|
end
|
|
674
|
-
elsif @question.character == :getc
|
|
675
|
-
@question.change_case(@input.getbyte.chr)
|
|
676
866
|
else
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
@
|
|
680
|
-
|
|
681
|
-
else
|
|
682
|
-
echo = if @question.echo == true
|
|
683
|
-
response
|
|
684
|
-
elsif @question.echo != false
|
|
685
|
-
@question.echo
|
|
867
|
+
raw_no_echo_mode
|
|
868
|
+
begin
|
|
869
|
+
if @question.character == :getc
|
|
870
|
+
response = @input.getbyte.chr
|
|
686
871
|
else
|
|
687
|
-
|
|
872
|
+
response = get_character(@input).chr
|
|
873
|
+
if @question.overwrite
|
|
874
|
+
@output.print("\r#{HighLine.Style(:erase_line).code}")
|
|
875
|
+
@output.flush
|
|
876
|
+
else
|
|
877
|
+
echo = if @question.echo == true
|
|
878
|
+
response
|
|
879
|
+
elsif @question.echo != false
|
|
880
|
+
@question.echo
|
|
881
|
+
else
|
|
882
|
+
""
|
|
883
|
+
end
|
|
884
|
+
say("#{echo}\n")
|
|
885
|
+
end
|
|
688
886
|
end
|
|
689
|
-
|
|
887
|
+
ensure
|
|
888
|
+
restore_mode
|
|
690
889
|
end
|
|
691
890
|
@question.change_case(response)
|
|
692
891
|
end
|
|
@@ -730,15 +929,17 @@ class HighLine
|
|
|
730
929
|
def wrap( text )
|
|
731
930
|
wrapped = [ ]
|
|
732
931
|
text.each_line do |line|
|
|
733
|
-
|
|
932
|
+
# take into account color escape sequences when wrapping
|
|
933
|
+
wrap_at = @wrap_at + (line.length - actual_length(line))
|
|
934
|
+
while line =~ /([^\n]{#{wrap_at + 1},})/
|
|
734
935
|
search = $1.dup
|
|
735
936
|
replace = $1.dup
|
|
736
|
-
if index = replace.rindex(" ",
|
|
937
|
+
if index = replace.rindex(" ", wrap_at)
|
|
737
938
|
replace[index, 1] = "\n"
|
|
738
939
|
replace.sub!(/\n[ \t]+/, "\n")
|
|
739
940
|
line.sub!(search, replace)
|
|
740
941
|
else
|
|
741
|
-
line[$~.begin(1) +
|
|
942
|
+
line[$~.begin(1) + wrap_at, 0] = "\n"
|
|
742
943
|
end
|
|
743
944
|
end
|
|
744
945
|
wrapped << line
|
|
@@ -751,6 +952,9 @@ class HighLine
|
|
|
751
952
|
# sequence escapes.
|
|
752
953
|
#
|
|
753
954
|
def actual_length( string_with_escapes )
|
|
754
|
-
string_with_escapes.gsub(/\e\[\d{1,2}m/, "").length
|
|
955
|
+
string_with_escapes.to_s.gsub(/\e\[\d{1,2}m/, "").length
|
|
755
956
|
end
|
|
756
957
|
end
|
|
958
|
+
|
|
959
|
+
require "highline/string_extensions"
|
|
960
|
+
|