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.3.0"
NOTHING =

Clears all colors

"\033[0m"
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!



112
113
114
# File 'lib/paint.rb', line 112

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



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

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 = string.gsub(
        "%{#{key}}",
        value.is_a?(Array) ?
        self.%(value, clear_color + 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



12
13
14
15
16
# File 'lib/paint.rb', line 12

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



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
100
101
102
103
# File 'lib/paint.rb', line 48

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



178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/paint.rb', line 178

def detect_mode
  if ENV['NO_COLOR'] # see https://no-color.org/
    0
  elsif RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ # windows
    if ENV['ANSICON']
      16
    elsif ENV['ConEmuANSI'] == 'ON'
      TRUE_COLOR
    else
      0
    end
  elsif ENV['TERM_PROGRAM'] == 'Apple_Terminal'
    256
  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



172
173
174
# File 'lib/paint.rb', line 172

def effect(effect_name)
  ANSI_EFFECTS[effect_name]
end

.random(background = false) ⇒ Object

Creates a random ANSI color



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

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



140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/paint.rb', line 140

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



154
155
156
157
158
159
160
161
162
# File 'lib/paint.rb', line 154

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)



165
166
167
168
169
# File 'lib/paint.rb', line 165

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



134
135
136
# File 'lib/paint.rb', line 134

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

.unpaint(string) ⇒ Object

Removes any color and effect strings



7
8
9
# File 'lib/paint/util.rb', line 7

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

.wrap(*ansi_codes) ⇒ Object

Adds ANSI sequence



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

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