highline 1.6.2 → 1.6.5

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -2,6 +2,30 @@
2
2
 
3
3
  Below is a complete listing of changes for each revision of HighLine.
4
4
 
5
+ == 1.6.5
6
+
7
+ * HighLine#list() now correctly handles empty lists (fix by Lachlan Dowding).
8
+ * HighLine#list() now supports <tt>:uneven_columns_across</tt> and
9
+ <tt>:uneven_columns_down</tt> modes.
10
+
11
+ == 1.6.4
12
+
13
+ * Add introspection methods to color_scheme: definition, keys, to_hash.
14
+ * Add tests for new methods.
15
+
16
+ == 1.6.3
17
+
18
+ * Add color NONE.
19
+ * Add RGB color capability.
20
+ * Made 'color' available as a class or instance method of HighLine, for
21
+ instance: HighLine.color("foo", :blue)) or highline_obj.color("foo", :blue)
22
+ are now both possible and equivalent.
23
+ * Add HighLine::String class with convenience methods: #color (alias
24
+ #foreground), #on (alias #background), colors, and styles. See
25
+ lib/string_extensions.rb.
26
+ * Add (optional) ability to extend String with the same convenience methods from
27
+ HighLine::String, using Highline.colorize_strings.
28
+
5
29
  == 1.6.2
6
30
 
7
31
  * Correctly handle STDIN being closed before we receive any data (fix by
data/Rakefile CHANGED
@@ -1,6 +1,6 @@
1
- require "rake/rdoctask"
1
+ require "rdoc/task"
2
2
  require "rake/testtask"
3
- require "rake/gempackagetask"
3
+ require "rubygems/package_task"
4
4
 
5
5
  require "rubygems"
6
6
 
@@ -12,7 +12,7 @@ task :default => [:test]
12
12
 
13
13
  Rake::TestTask.new do |test|
14
14
  test.libs << "test"
15
- test.test_files = [ "test/ts_all.rb" ]
15
+ test.test_files = [ "test/ts_all.rb"]
16
16
  test.verbose = true
17
17
  end
18
18
 
@@ -63,7 +63,7 @@ minutes of work.
63
63
  END_DESC
64
64
  end
65
65
 
66
- Rake::GemPackageTask.new(spec) do |pkg|
66
+ Gem::PackageTask.new(spec) do |pkg|
67
67
  pkg.need_zip = true
68
68
  pkg.need_tar = true
69
69
  end
@@ -17,6 +17,7 @@ require "highline/system_extensions"
17
17
  require "highline/question"
18
18
  require "highline/menu"
19
19
  require "highline/color_scheme"
20
+ require "highline/style"
20
21
 
21
22
  #
22
23
  # A HighLine object is a "high-level line oriented" shell over an input and an
@@ -29,7 +30,7 @@ require "highline/color_scheme"
29
30
  #
30
31
  class HighLine
31
32
  # The version of the installed library.
32
- VERSION = "1.6.2".freeze
33
+ VERSION = "1.6.5".freeze
33
34
 
34
35
  # An internal HighLine error. User code does not need to trap this.
35
36
  class QuestionError < StandardError
@@ -49,6 +50,13 @@ class HighLine
49
50
  @@use_color
50
51
  end
51
52
 
53
+ # For checking if the current version of HighLine supports RGB colors
54
+ # Usage: HighLine.supports_rgb_color? rescue false # rescue for compatibility with older versions
55
+ # Note: color usage also depends on HighLine.use_color being set
56
+ def self.supports_rgb_color?
57
+ true
58
+ end
59
+
52
60
  # The setting used to disable EOF tracking.
53
61
  @@track_eof = true
54
62
 
@@ -83,62 +91,85 @@ class HighLine
83
91
  #
84
92
  # Embed in a String to clear all previous ANSI sequences. This *MUST* be
85
93
  # 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"
94
+ #
95
+
96
+ ERASE_LINE_STYLE = Style.new(:name=>:erase_line, :builtin=>true, :code=>"\e[K") # Erase the current line of terminal output
97
+ ERASE_CHAR_STYLE = Style.new(:name=>:erase_char, :builtin=>true, :code=>"\e[P") # Erase the character under the cursor.
98
+ CLEAR_STYLE = Style.new(:name=>:clear, :builtin=>true, :code=>"\e[0m") # Clear color settings
99
+ RESET_STYLE = Style.new(:name=>:reset, :builtin=>true, :code=>"\e[0m") # Alias for CLEAR.
100
+ BOLD_STYLE = Style.new(:name=>:bold, :builtin=>true, :code=>"\e[1m") # Bold; Note: bold + a color works as you'd expect,
101
+ # for example bold black. Bold without a color displays
102
+ # the system-defined bold color (e.g. red on Mac iTerm)
103
+ DARK_STYLE = Style.new(:name=>:dark, :builtin=>true, :code=>"\e[2m") # Dark; support uncommon
104
+ UNDERLINE_STYLE = Style.new(:name=>:underline, :builtin=>true, :code=>"\e[4m") # Underline
105
+ UNDERSCORE_STYLE = Style.new(:name=>:underscore, :builtin=>true, :code=>"\e[4m") # Alias for UNDERLINE
106
+ BLINK_STYLE = Style.new(:name=>:blink, :builtin=>true, :code=>"\e[5m") # Blink; support uncommon
107
+ REVERSE_STYLE = Style.new(:name=>:reverse, :builtin=>true, :code=>"\e[7m") # Reverse foreground and background
108
+ CONCEALED_STYLE = Style.new(:name=>:concealed, :builtin=>true, :code=>"\e[8m") # Concealed; support uncommon
109
+
110
+ STYLES = %w{CLEAR RESET BOLD DARK UNDERLINE UNDERSCORE BLINK REVERSE CONCEALED}
111
+
112
+ # These RGB colors are approximate; see http://en.wikipedia.org/wiki/ANSI_escape_code
113
+ BLACK_STYLE = Style.new(:name=>:black, :builtin=>true, :code=>"\e[30m", :rgb=>[ 0, 0, 0])
114
+ RED_STYLE = Style.new(:name=>:red, :builtin=>true, :code=>"\e[31m", :rgb=>[128, 0, 0])
115
+ GREEN_STYLE = Style.new(:name=>:green, :builtin=>true, :code=>"\e[32m", :rgb=>[ 0,128, 0])
116
+ BLUE_STYLE = Style.new(:name=>:blue, :builtin=>true, :code=>"\e[34m", :rgb=>[ 0, 0,128])
117
+ YELLOW_STYLE = Style.new(:name=>:yellow, :builtin=>true, :code=>"\e[33m", :rgb=>[128,128, 0])
118
+ MAGENTA_STYLE = Style.new(:name=>:magenta, :builtin=>true, :code=>"\e[35m", :rgb=>[128, 0,128])
119
+ CYAN_STYLE = Style.new(:name=>:cyan, :builtin=>true, :code=>"\e[36m", :rgb=>[ 0,128,128])
120
+ # On Mac OSX Terminal, white is actually gray
121
+ WHITE_STYLE = Style.new(:name=>:white, :builtin=>true, :code=>"\e[37m", :rgb=>[192,192,192])
122
+ # Alias for WHITE, since WHITE is actually a light gray on Macs
123
+ GRAY_STYLE = Style.new(:name=>:gray, :builtin=>true, :code=>"\e[37m", :rgb=>[192,192,192])
124
+ # On Mac OSX Terminal, this is black foreground, or bright white background.
125
+ # Also used as base for RGB colors, if available
126
+ NONE_STYLE = Style.new(:name=>:none, :builtin=>true, :code=>"\e[38m", :rgb=>[ 0, 0, 0])
127
+
128
+ BASIC_COLORS = %w{BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE GRAY NONE}
129
+
130
+ colors = BASIC_COLORS.dup
131
+ BASIC_COLORS.each do |color|
132
+ bright_color = "BRIGHT_#{color}"
133
+ colors << bright_color
134
+ const_set bright_color+'_STYLE', const_get(color + '_STYLE').bright
135
+ end
136
+ COLORS = colors
137
+
138
+ colors.each do |color|
139
+ const_set color, const_get("#{color}_STYLE").code
140
+ const_set "ON_#{color}_STYLE", const_get("#{color}_STYLE").on
141
+ const_set "ON_#{color}", const_get("ON_#{color}_STYLE").code
142
+ end
143
+ ON_NONE_STYLE.rgb = [255,255,255] # Override; white background
144
+
145
+ STYLES.each do |style|
146
+ const_set style, const_get("#{style}_STYLE").code
147
+ end
148
+
149
+ # For RGB colors:
150
+ def self.const_missing(name)
151
+ if name.to_s =~ /^(ON_)?(RGB_)([A-F0-9]{6})(_STYLE)?$/ # RGB color
152
+ on = $1
153
+ suffix = $4
154
+ if suffix
155
+ code_name = $1.to_s + $2 + $3
156
+ else
157
+ code_name = name.to_s
158
+ end
159
+ style_name = code_name + '_STYLE'
160
+ style = Style.rgb($3)
161
+ style = style.on if on
162
+ const_set(style_name, style)
163
+ const_set(code_name, style.code)
164
+ if suffix
165
+ style
166
+ else
167
+ style.code
168
+ end
169
+ else
170
+ raise NameError, "Bad color or uninitialized constant #{name}"
171
+ end
172
+ end
142
173
 
143
174
  #
144
175
  # Create an instance of HighLine, connected to the streams _input_
@@ -328,19 +359,34 @@ class HighLine
328
359
  # This method returns the original _string_ unchanged if HighLine::use_color?
329
360
  # is +false+.
330
361
  #
331
- def color( string, *colors )
332
- return string unless self.class.use_color?
333
-
334
- colors.map! do |c|
335
- if self.class.using_color_scheme? and self.class.color_scheme.include? c
336
- self.class.color_scheme[c]
337
- elsif c.is_a? Symbol
338
- self.class.const_get(c.to_s.upcase)
339
- else
340
- c
341
- end
342
- end
343
- "#{colors.flatten.join}#{string}#{CLEAR}"
362
+ def self.color( string, *colors )
363
+ return string unless self.use_color?
364
+ Style(*colors).color(string)
365
+ end
366
+
367
+ # In case you just want the color code, without the embedding and the CLEAR
368
+ def self.color_code(*colors)
369
+ Style(*colors).code
370
+ end
371
+
372
+ # Works as an instance method, same as the class method
373
+ def color_code(*colors)
374
+ self.class.color_code(*colors)
375
+ end
376
+
377
+ # Works as an instance method, same as the class method
378
+ def color(*args)
379
+ self.class.color(*args)
380
+ end
381
+
382
+ # Remove color codes from a string
383
+ def self.uncolor(string)
384
+ Style.uncolor(string)
385
+ end
386
+
387
+ # Works as an instance method, same as the class method
388
+ def uncolor(string)
389
+ self.class.uncolor(string)
344
390
  end
345
391
 
346
392
  #
@@ -352,20 +398,25 @@ class HighLine
352
398
  # to list. A specified _mode_ controls how that list is formed and _option_
353
399
  # has different effects, depending on the _mode_. Recognized modes are:
354
400
  #
355
- # <tt>:columns_across</tt>:: _items_ will be placed in columns, flowing
356
- # from left to right. If given, _option_ is the
357
- # number of columns to be used. When absent,
358
- # columns will be determined based on _wrap_at_
359
- # or a default of 80 characters.
360
- # <tt>:columns_down</tt>:: Identical to <tt>:columns_across</tt>, save
361
- # flow goes down.
362
- # <tt>:inline</tt>:: All _items_ are placed on a single line. The
363
- # last two _items_ are separated by _option_ or
364
- # a default of " or ". All other _items_ are
365
- # separated by ", ".
366
- # <tt>:rows</tt>:: The default mode. Each of the _items_ is
367
- # placed on it's own line. The _option_
368
- # parameter is ignored in this mode.
401
+ # <tt>:columns_across</tt>:: _items_ will be placed in columns,
402
+ # flowing from left to right. If given,
403
+ # _option_ is the number of columns to be
404
+ # used. When absent, columns will be
405
+ # determined based on _wrap_at_ or a
406
+ # default of 80 characters.
407
+ # <tt>:columns_down</tt>:: Identical to <tt>:columns_across</tt>,
408
+ # save flow goes down.
409
+ # <tt>:uneven_columns_across</tt>:: Like <tt>:columns_across</tt> but each
410
+ # column is sized independently.
411
+ # <tt>:uneven_columns_down</tt>:: Like <tt>:columns_down</tt> but each
412
+ # column is sized independently.
413
+ # <tt>:inline</tt>:: All _items_ are placed on a single line.
414
+ # The last two _items_ are separated by
415
+ # _option_ or a default of " or ". All
416
+ # other _items_ are separated by ", ".
417
+ # <tt>:rows</tt>:: The default mode. Each of the _items_ is
418
+ # placed on it's own line. The _option_
419
+ # parameter is ignored in this mode.
369
420
  #
370
421
  # Each member of the _items_ Array is passed through ERb and thus can contain
371
422
  # their own expansions. Color escape expansions do not contribute to the
@@ -376,58 +427,157 @@ class HighLine
376
427
  ERB.new(item, nil, "%").result(binding)
377
428
  end
378
429
 
379
- case mode
380
- when :inline
381
- option = " or " if option.nil?
430
+ if items.empty?
431
+ ""
432
+ else
433
+ case mode
434
+ when :inline
435
+ option = " or " if option.nil?
436
+
437
+ if items.size == 1
438
+ items.first
439
+ else
440
+ items[0..-2].join(", ") + "#{option}#{items.last}"
441
+ end
442
+ when :columns_across, :columns_down
443
+ max_length = actual_length(
444
+ items.max { |a, b| actual_length(a) <=> actual_length(b) }
445
+ )
446
+
447
+ if option.nil?
448
+ limit = @wrap_at || 80
449
+ option = (limit + 2) / (max_length + 2)
450
+ end
451
+
452
+ items = items.map do |item|
453
+ pad = max_length + (item.length - actual_length(item))
454
+ "%-#{pad}s" % item
455
+ end
456
+ row_count = (items.size / option.to_f).ceil
382
457
 
383
- case items.size
384
- when 0
385
- ""
386
- when 1
387
- items.first
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
458
+ if mode == :columns_across
459
+ rows = Array.new(row_count) { Array.new }
460
+ items.each_with_index do |item, index|
461
+ rows[index / option] << item
462
+ end
402
463
 
403
- items = items.map do |item|
404
- pad = max_length + (item.length - actual_length(item))
405
- "%-#{pad}s" % item
406
- end
407
- row_count = (items.size / option.to_f).ceil
464
+ rows.map { |row| row.join(" ") + "\n" }.join
465
+ else
466
+ columns = Array.new(option) { Array.new }
467
+ items.each_with_index do |item, index|
468
+ columns[index / row_count] << item
469
+ end
408
470
 
409
- if mode == :columns_across
410
- rows = Array.new(row_count) { Array.new }
411
- items.each_with_index do |item, index|
412
- rows[index / option] << item
471
+ list = ""
472
+ columns.first.size.times do |index|
473
+ list << columns.map { |column| column[index] }.
474
+ compact.join(" ") + "\n"
475
+ end
476
+ list
413
477
  end
478
+ when :uneven_columns_across
479
+ if option.nil?
480
+ limit = @wrap_at || 80
481
+ items.size.downto(1) do |column_count|
482
+ row_count = (items.size / column_count.to_f).ceil
483
+ rows = Array.new(row_count) { Array.new }
484
+ items.each_with_index do |item, index|
485
+ rows[index / column_count] << item
486
+ end
414
487
 
415
- rows.map { |row| row.join(" ") + "\n" }.join
416
- else
417
- columns = Array.new(option) { Array.new }
418
- items.each_with_index do |item, index|
419
- columns[index / row_count] << item
488
+ widths = Array.new(column_count, 0)
489
+ rows.each do |row|
490
+ row.each_with_index do |field, column|
491
+ size = field.size
492
+ widths[column] = size if size > widths[column]
493
+ end
494
+ end
495
+
496
+ if column_count == 1 or
497
+ widths.inject(0) { |sum, n| sum + n + 2 } <= limit + 2
498
+ return rows.map { |row|
499
+ row.zip(widths).map { |field, i| "%-#{i}s" % field }.
500
+ join(" ") + "\n"
501
+ }.join
502
+ end
503
+ end
504
+ else
505
+ row_count = (items.size / option.to_f).ceil
506
+ rows = Array.new(row_count) { Array.new }
507
+ items.each_with_index do |item, index|
508
+ rows[index / option] << item
509
+ end
510
+
511
+ widths = Array.new(option, 0)
512
+ rows.each do |row|
513
+ row.each_with_index do |field, column|
514
+ size = field.size
515
+ widths[column] = size if size > widths[column]
516
+ end
517
+ end
518
+
519
+ return rows.map { |row|
520
+ row.zip(widths).map { |field, i| "%-#{i}s" % field }.join(" ") +
521
+ "\n"
522
+ }.join
420
523
  end
421
-
422
- list = ""
423
- columns.first.size.times do |index|
424
- list << columns.map { |column| column[index] }.
425
- compact.join(" ") + "\n"
524
+ when :uneven_columns_down
525
+ if option.nil?
526
+ limit = @wrap_at || 80
527
+ items.size.downto(1) do |column_count|
528
+ row_count = (items.size / column_count.to_f).ceil
529
+ columns = Array.new(column_count) { Array.new }
530
+ items.each_with_index do |item, index|
531
+ columns[index / row_count] << item
532
+ end
533
+
534
+ widths = Array.new(column_count, 0)
535
+ columns.each_with_index do |column, i|
536
+ column.each do |field|
537
+ size = field.size
538
+ widths[i] = size if size > widths[i]
539
+ end
540
+ end
541
+
542
+ if column_count == 1 or
543
+ widths.inject(0) { |sum, n| sum + n + 2 } <= limit + 2
544
+ list = ""
545
+ columns.first.size.times do |index|
546
+ list << columns.zip(widths).
547
+ map { |column, width| "%-#{width}s" %
548
+ column[index] }.
549
+ compact.join(" ").strip + "\n"
550
+ end
551
+ return list
552
+ end
553
+ end
554
+ else
555
+ row_count = (items.size / option.to_f).ceil
556
+ columns = Array.new(option) { Array.new }
557
+ items.each_with_index do |item, index|
558
+ columns[index / row_count] << item
559
+ end
560
+
561
+ widths = Array.new(option, 0)
562
+ columns.each_with_index do |column, i|
563
+ column.each do |field|
564
+ size = field.size
565
+ widths[i] = size if size > widths[i]
566
+ end
567
+ end
568
+
569
+ list = ""
570
+ columns.first.size.times do |index|
571
+ list << columns.zip(widths).
572
+ map { |column, width| "%-#{width}s" %
573
+ column[index] }.
574
+ compact.join(" ").strip + "\n"
575
+ end
576
+ return list
426
577
  end
427
- list
578
+ else
579
+ items.map { |i| "#{i}\n" }.join
428
580
  end
429
- else
430
- items.map { |i| "#{i}\n" }.join
431
581
  end
432
582
  end
433
583
 
@@ -540,11 +690,11 @@ class HighLine
540
690
  @answers << ask(@question)
541
691
  @gather -= 1
542
692
  end
543
- when String, Regexp
693
+ when ::String, Regexp
544
694
  @answers << ask(@question)
545
695
 
546
696
  original_question.question = ""
547
- until (@gather.is_a?(String) and @answers.last.to_s == @gather) or
697
+ until (@gather.is_a?(::String) and @answers.last.to_s == @gather) or
548
698
  (@gather.is_a?(Regexp) and @answers.last.to_s =~ @gather)
549
699
  @question = original_question
550
700
  @answers << ask(@question)
@@ -761,3 +911,6 @@ class HighLine
761
911
  string_with_escapes.gsub(/\e\[\d{1,2}m/, "").length
762
912
  end
763
913
  end
914
+
915
+ require "highline/string_extensions"
916
+