highline 1.6.2 → 1.6.5

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/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
+