Module: Paint

Defined in:
lib/paint.rb,
lib/paint/util.rb,
lib/paint/version.rb,
lib/paint/constants.rb,
lib/paint/rgb_colors.rb

Constant Summary collapse

VERSION =
"2.1.1"
NOTHING =

Clears all colors

"\033[0m".freeze
TRUE_COLOR =

Number of possible colors in TRUE COLOR mode

0xFFFFFF
ANSI_COLORS =

Basic colors (often, the color differs when using the bright effect) Final color will be 30 + value for foreground and 40 + value for background

{
  :black   => 0,
  :red     => 1,
  :green   => 2,
  :yellow  => 3,
  :blue    => 4,
  :magenta => 5,
  :cyan    => 6,
  :white   => 7, :gray => 7,
  :default => 9,
}.freeze
ANSI_COLORS_FOREGROUND =
{
  :black   => 30,
  :red     => 31,
  :green   => 32,
  :yellow  => 33,
  :blue    => 34,
  :magenta => 35,
  :cyan    => 36,
  :white   => 37, :gray => 37,
  :default => 39,
}.freeze
ANSI_COLORS_BACKGROUND =
{
  :black   => 40,
  :red     => 41,
  :green   => 42,
  :yellow  => 43,
  :blue    => 44,
  :magenta => 45,
  :cyan    => 46,
  :white   => 47, :gray => 47,
  :default => 49,
}.freeze
ANSI_EFFECTS =

Terminal effects - most of them are not supported ;) See en.wikipedia.org/wiki/ANSI_escape_code

{
  :reset         => 0,  :nothing         => 0,  # usually supported
  :bright        => 1,  :bold            => 1,  # usually supported
  :faint         => 2,
  :italic        => 3,
  :underline     => 4,                          # usually supported
  :blink         => 5,  :slow_blink      => 5,
  :rapid_blink   => 6,
  :inverse       => 7,  :swap            => 7,  # usually supported
  :conceal       => 8,  :hide            => 9,
  :default_font  => 10,
  :font0 => 10, :font1 => 11, :font2 => 12, :font3 => 13, :font4 => 14,
  :font5 => 15, :font6 => 16, :font7 => 17, :font8 => 18, :font9 => 19,
  :fraktur       => 20,
  :bright_off    => 21, :bold_off        => 21, :double_underline => 21,
  :clean         => 22,
  :italic_off    => 23, :fraktur_off     => 23,
  :underline_off => 24,
  :blink_off     => 25,
  :inverse_off   => 26, :positive        => 26,
  :conceal_off   => 27, :show            => 27, :reveal           => 27,
  :crossed_off   => 29, :crossed_out_off => 29,
  :frame         => 51,
  :encircle      => 52,
  :overline      => 53,
  :frame_off     => 54, :encircle_off    => 54,
  :overline_off  => 55,
}.freeze
RGB_COLORS_ANSI =

A list of color names for standard ansi colors, needed for 16/8 color fallback mode See en.wikipedia.org/wiki/ANSI_escape_code#Colors

{
  :black   => [  0,   0,   0],
  :red     => [205,   0,   0],
  :green   => [  0, 205,   0],
  :yellow  => [205, 205,   0],
  :blue    => [  0,   0, 238],
  :magenta => [205,   0, 205],
  :cyan    => [  0, 205, 205],
  :white   => [229, 229, 229], :gray => [229, 229, 229],
}.each { |k, v| v.freeze }.freeze
RGB_COLORS_ANSI_BRIGHT =

A list of color names for standard bright ansi colors, needed for 16 color fallback mode See en.wikipedia.org/wiki/ANSI_escape_code#Colors

{
  :black   => [127, 127, 127],
  :red     => [255,   0,   0],
  :green   => [  0, 255,   0],
  :yellow  => [255, 255,   0],
  :blue    => [ 92,  92, 255],
  :magenta => [255,   0, 255],
  :cyan    => [  0, 255, 255],
  :white   => [255, 255, 255], :gray => [255, 255, 255],
}.each { |k, v| v.freeze }.freeze
RGB_COLORS_INDEX_FILENAME =
File.dirname(__FILE__) + "/../../data/rgb_colors.marshal.gz"

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.modeObject

This variable influences the color code generation Currently supported values:

  • 0xFFFFFF - 24-bit (~16 million) colors, aka truecolor

  • 256 - 256 colors

  • 16 - only ansi colors and bright effect

  • 8 - only ansi colors

  • 0 - no colorization!



108
109
110
# File 'lib/paint.rb', line 108

def mode
  @mode
end

Class Method Details

.%(paint_arguments, clear_color = NOTHING) ⇒ Object

Takes an array with string and color options and colorizes the string, extended version with nesting and substitution support



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/paint.rb', line 18

def %(paint_arguments, clear_color = NOTHING)
  string, *options = paint_arguments
  return string.to_s if options.empty?
  substitutions = options.pop if options[-1].is_a?(Hash)
  options = options[0] if options.size == 1 && !options[0].respond_to?(:to_ary)
  current_color = @cache[options]

  # Substitutions & Nesting
  if substitutions
    substitutions.each{ |key, value|
      string.gsub!(
        "%{#{key}}",
        (value.is_a?(Array) ? self.%(value, current_color) : value.to_s)
      )
    }
  end

  # Wrap string (if Paint.mode > 0)
  if @mode.zero?
    string.to_s
  else
    current_color + string.to_s + clear_color
  end
end

.[](string, *options) ⇒ Object

Takes a string and color options and colorizes the string, fast version without nesting



10
11
12
13
14
# File 'lib/paint.rb', line 10

def [](string, *options)
  return string.to_s if @mode.zero? || options.empty?
  options = options[0] if options.size == 1 && !options[0].respond_to?(:to_ary)
  @cache[options] + string.to_s + NOTHING
end

.color(*options) ⇒ Object

Transforms options into the desired color. Used by @cache



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/paint.rb', line 44

def color(*options)
  return '' if @mode.zero? || options.empty?
  mix = []
  color_seen = false
  colors = ANSI_COLORS_FOREGROUND

  options.each{ |option|
    case option
    when Symbol
      if color = colors[option]
        mix << color
        color_seen = :set
      elsif ANSI_EFFECTS.key?(option)
        mix << effect(option)
      else
        raise ArgumentError, "Unknown color or effect: #{ option }"
      end

    when Array
      if option.size == 3 && option.all?{ |n| n.is_a? Numeric }
        mix << rgb(*[*option, color_seen])
        color_seen = :set
      else
        raise ArgumentError, "Array argument must contain 3 numerals"
      end

    when ::String
      if option =~ /\A#?(?<hex_color>[[:xdigit:]]{3}{1,2})\z/ # 3 or 6 hex chars
        mix << rgb_hex($~[:hex_color], color_seen)
        color_seen = :set
      else
        mix << rgb_name(option, color_seen)
        color_seen = :set
      end

    when Numeric
      integer = option.to_i
      color_seen = :set if (30..49).include?(integer)
      mix << integer

    when nil
      color_seen = :set

    else
      raise ArgumentError, "Invalid argument: #{ option.inspect }"

    end

    if color_seen == :set
      colors = ANSI_COLORS_BACKGROUND
      color_seen = true
    end
  }

  wrap(*mix)
end

.detect_modeObject

Determine supported colors Note: there’s no reliable test for 24-bit color support



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/paint.rb', line 174

def detect_mode
  if RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ # windows
    if ENV['ANSICON']
      16
    elsif ENV['ConEmuANSI'] == 'ON'
      TRUE_COLOR
    else
      0
    end
  else
    case ENV['TERM']
    when /^rxvt-(?:.*)-256color$/
      256
    when /-color$/, /^rxvt/
      16
    else # optimistic default
      TRUE_COLOR
    end
  end
end

.effect(effect_name) ⇒ Object

Creates the specified effect by looking it up in Paint::ANSI_EFFECTS



168
169
170
# File 'lib/paint.rb', line 168

def effect(effect_name)
  ANSI_EFFECTS[effect_name]
end

.random(background = false) ⇒ Object

Creates a random ANSI color



10
11
12
# File 'lib/paint/util.rb', line 10

def random(background = false)
  (background ? 40 : 30) + rand(8)
end

.rgb(red, green, blue, background = false) ⇒ Object

If not true_color, creates a 256-compatible color from rgb values, otherwise, an exact 24-bit color



136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/paint.rb', line 136

def rgb(red, green, blue, background = false)
  case @mode
  when 8
    "#{background ? 4 : 3}#{rgb_to_ansi(red, green, blue, false)}"
  when 16
    "#{background ? 4 : 3}#{rgb_to_ansi(red, green, blue, true)}"
  when 256
    "#{background ? 48 : 38}#{rgb_to_256(red, green, blue)}"
  when TRUE_COLOR
    "#{background ? 48 : 38}#{rgb_true(red, green, blue)}"
  end
end

.rgb_hex(string, background = false) ⇒ Object

Creates RGB color from a HTML-like color definition string



150
151
152
153
154
155
156
157
158
# File 'lib/paint.rb', line 150

def rgb_hex(string, background = false)
  case string.size
  when 6
    color_code = string.each_char.each_slice(2).map{ |hex_color| hex_color.join.to_i(16) }
  when 3
    color_code = string.each_char.map{ |hex_color_half| (hex_color_half*2).to_i(16) }
  end
  rgb(*[*color_code, background])
end

.rgb_name(color_name, background = false) ⇒ Object

Creates a RGB from a name found in Paint::RGB_COLORS (based on rgb.txt)



161
162
163
164
165
# File 'lib/paint.rb', line 161

def rgb_name(color_name, background = false)
  if color_code = RGB_COLORS[color_name]
    rgb(*[*color_code, background])
  end
end

.simple(color_name, background = false) ⇒ Object

Creates simple ansi color by looking it up on Paint::ANSI_COLORS



130
131
132
# File 'lib/paint.rb', line 130

def simple(color_name, background = false)
  (background ? 40 : 30) + ANSI_COLORS[color_name]
end

.unpaint(string) ⇒ Object

Removes any color and effect strings



5
6
7
# File 'lib/paint/util.rb', line 5

def unpaint(string)
  string.gsub(/\e\[(?:[0-9];?)+m/, '')
end

.wrap(*ansi_codes) ⇒ Object

Adds ANSI sequence



125
126
127
# File 'lib/paint.rb', line 125

def wrap(*ansi_codes)
  "\033[" + ansi_codes*";" + "m"
end