module Paint

Constants

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

ANSI_COLORS_BACKGROUND
ANSI_COLORS_FOREGROUND
ANSI_EFFECTS

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

NOTHING
RGB_COLORS

A list of color names, based on X11's rgb.txt Can be used with ::[] by passing a string containing the color name See Paint::Util::update_rgb_colors for generating

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

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

VERSION

Attributes

mode[R]

This variable influences the color code generation Currently supported values:

  • 256 - 256 colors

  • 16 - only ansi colors and bright effect

  • 8 - only ansi colors

  • 0 - no colorization!

Public Class Methods

[](string, *options) click to toggle source

Takes a string and color options and colorizes the string See README.rdoc for details

# File lib/paint.rb, line 83
def [](string, *options)
  return string.to_s if @mode.zero? || options.empty?
  options = options.first if options.size == 1 && !options.first.respond_to?(:to_ary)
  @cache[options] + string.to_s + NOTHING
end
color(*options) click to toggle source

Transforms options into the desired color. Used by @cache

# File lib/paint.rb, line 90
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 =~ /^#?(?:[0-9a-f]{3}){1,2}$/i
        mix << hex(option, 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_mode() click to toggle source

Determine supported colors

# File lib/paint.rb, line 204
def detect_mode
  if RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ # windows
    if ENV['ANSICON']
      16
    elsif ENV['ConEmuANSI'] == 'ON'
      256
    else
      0
    end
  else
    case ENV['TERM']
    when /-256color$/, 'xterm'
      256
    when /-color$/, 'rxvt'
      16
    else # optimistic default
      256
    end
  end
end
effect(effect_name) click to toggle source

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

# File lib/paint.rb, line 199
def effect(effect_name)
  ANSI_EFFECTS[effect_name]
end
hex(source, background = false) click to toggle source

Creates 256-compatible color from a html-like color string

# File lib/paint.rb, line 176
def hex(source, background = false)
  string = source.tr '#',''
  color_code = if string.size == 6
    string.each_char.each_slice(2).map{ |hex_color| hex_color.join.to_i(16) }
  else
    string.each_char.map{ |hex_color_half| (hex_color_half*2).to_i(16) }
  end
  rgb(*[*color_code, background])
end
mode=(val) click to toggle source
# File lib/paint.rb, line 154
def mode=(val) @cache.clear; @mode = val end
rainbow() click to toggle source

Tries to print all 256 colors

# File lib/paint/util.rb, line 12
def rainbow
  (0...256).each{ |color|
    print Paint[' ', 48, 5, color] # print empty bg color field
  }
  puts
end
random(background = false) click to toggle source

Creates a random ansi color

# File lib/paint.rb, line 194
def random(background = false)
  (background ? 40 : 30) + rand(8)
end
rgb(red, green, blue, background = false) click to toggle source

Creates a 256-compatible color from rgb values

# File lib/paint.rb, line 167
def rgb(red, green, blue, background = false)
  if @mode == 8 || @mode == 16
    "#{background ? 4 : 3}#{rgb_like_value(red, green, blue, @mode == 16)}"
  else
    "#{background ? 48 : 38}#{rgb_value(red, green, blue)}"
  end
end
rgb_name(color_name, background = false) click to toggle source

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

# File lib/paint.rb, line 187
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) click to toggle source

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

# File lib/paint.rb, line 162
def simple(color_name, background = false)
  (background ? 40 : 30) + ANSI_COLORS[color_name]
end
unpaint(string) click to toggle source

Removes any color and effect strings

# File lib/paint/util.rb, line 7
def unpaint(string)
  string.gsub(/\e\[(?:[0-9];?)+m/, '')
end
update_rgb_colors(path = '/etc/X11/rgb.txt') click to toggle source

Updates color names

# File lib/paint/util.rb, line 20
def update_rgb_colors(path = '/etc/X11/rgb.txt')
  if File.file?(path)
    Paint::RGB_COLORS.clear

    File.open(path, 'r') do |file|
      file.each_line{ |line|
        line.chomp =~ /(\d+)\s+(\d+)\s+(\d+)\s+(.*)$/
        Paint::RGB_COLORS[$4] = [$1.to_i, $2.to_i, $3.to_i] if $4
      }
    end
  else
    raise ArgumentError, "Not a valid file: #{path}"
  end
end
wrap(*ansi_codes) click to toggle source

Adds ansi sequence

# File lib/paint.rb, line 157
def wrap(*ansi_codes)
  "\033[" + ansi_codes*";" + "m"
end

Private Class Methods

distance(rgb1, rgb2) click to toggle source
# File lib/paint.rb, line 266
def distance(rgb1, rgb2)
  rgb1.zip(rgb2).inject(0){ |acc, (cur1, cur2)|
    acc + (cur1 - cur2)**2
  }
end
rgb_like_value(red, green, blue, use_bright = false) click to toggle source

Returns ansi color matching an rgb value, without fore-/background information See mail.python.org/pipermail/python-list/2008-December/1150496.html

# File lib/paint.rb, line 252
def rgb_like_value(red, green, blue, use_bright = false)
  color_pool =  RGB_COLORS_ANSI.values
  color_pool += RGB_COLORS_ANSI_BRIGHT.values if use_bright

  ansi_color_rgb = color_pool.min_by{ |col| distance([red, green, blue],col) }
  key_method = RUBY_VERSION < "1.9" ? :index : :key
  if ansi_color = RGB_COLORS_ANSI.send(key_method, ansi_color_rgb)
    ANSI_COLORS[ansi_color]
  else
    ansi_color = RGB_COLORS_ANSI_BRIGHT.send(key_method, ansi_color_rgb)
    "#{ANSI_COLORS[ansi_color]};1"
  end
end
rgb_value(red, green, blue) click to toggle source

Returns nearest supported 256-color an rgb value, without fore-/background information Inspired by the rainbow gem

# File lib/paint.rb, line 229
def rgb_value(red, green, blue)
  gray_possible = true
  sep = 42.5

  while gray_possible
    if red < sep || green < sep || blue < sep
      gray = red < sep && green < sep && blue < sep
      gray_possible = false
    end
    sep += 42.5
  end

  if gray
    ";5;#{ 232 + ((red.to_f + green.to_f + blue.to_f)/33).round }"
  else # rgb
    ";5;#{ [16, *[red, green, blue].zip([36, 6, 1]).map{ |color, mod|
      (6 * (color.to_f / 256)).to_i * mod
    }].inject(:+) }"
  end
end