Get to Know a Gem: Colored

Intro

I did plan on exploring the Annotate but I'm a little pressed for time this week and annotate is fairly complex. Instead I've chosen the simple but useful gem Colored that's been around for several years created by Github cofounder Chris Wanstrath. The gem allows a user to color a puts statement and add various other typographic elements. The code of the gem is in a single colored.rb file. So the foloowing explanation will be rather brief.

Colored module

Colored is composed of a single module fittingly named Colored that dynamically uses two constants COLORS and EXTRAS to define methods such as red, on_red, and red_on_black:

COLORS.each do |color, value|
  define_method(color) do 
    colorize(self, :foreground => color)
  end

  define_method("on_#{color}") do
    colorize(self, :background => color)
  end

  COLORS.each do |highlight, value|
    next if color == highlight
    define_method("#{color}_on_#{highlight}") do
      colorize(self, :foreground => color, :background => highlight)
    end
  end
end
EXTRAS.each do |extra, value|
  next if extra == 'clear'
  define_method(extra) do 
    colorize(self, :extra => extra)
  end
end

The colorize method is responsible for taking its paramters and composing a string of the initial string and the various string codes for the foreground, background, and typography. For instance:

colorize('hello', foreground: 'red') => "\e[31mhello\e[0m" 

The method itself looks like this:

def colorize(string, options = {})
  colored = [color(options[:foreground]), color("on_#{options[:background]}"), extra(options[:extra])].compact * ''
  colored << string
  colored << extra(:clear)   end

The initial colored array is composed of several calls to the color and extras method. The color method:

def color(color_name)
  background = color_name.to_s =~ /on_/
  color_name = color_name.to_s.sub('on_', '')
  return unless color_name && COLORS[color_name]
  "\e[#{COLORS[color_name] + (background ? 10 : 0)}m" 
end

is responsible for taking a color_name, such as 'black' or 'on_black', and returning the corresponding string color code. color also uses a regex for the 'on_' in order to determine whether the color_name passed is either a background or foreground color.

The extras method does much the same but for the typography options:

def extra(extra_name)
  extra_name = extra_name.to_s
  "\e[#{EXTRAS[extra_name]}m" if EXTRAS[extra_name]
end

Now colorize takes the array of string codes calls compact on it and then calls the * operator with '' passed.

colored = ["\e[31m", "\e[41m", "\e[1m"].compact * ''

I was unfamiliar with using * and a string paramter. This infact is an equivalent the to the Array join(str) that returns a given array as a string separated by the str paramter.

Thus colored is equal to "\e[31m\e[41m\e[1m". The method then appends the actual string that needs to be colored to itself, colored << string and finally adds the clear string code to the end so that the styling doesn't spill over to strings that may be concatenated with the colored string.

As mentioned earlier, colorize('hello', foreground: 'red') results in "\e[31mhello\e[0m". If in your console set a new string equal to that value and pass it to puts it will print 'hello' in red text.

That is the entirety of the Colored module. It makes effective use of dynamic programming to give the end user plenty of various options in order to color text in various ways.

Extending String

Colored would be cumbersome to use unless its methods were implemented on the String class itself. Colored achieves this with the simplest of method calls:

String.send(:include, Colored)

Thus, once Colored is installed it can easily be called on string objects, "this".red, "this".on_red, etc.

Conclusion

I just happened to stumble upon Colored as I was looking through a Gemfile.lock on one of my projects. Since I have limited time this week, Colored was a great gem to quickly understand how it works and in addition learn a few things about string color codes and the array * operator. It's no surprise that this gem hasn't been updated in five years, it provides a feature, does it well, and isn't intended to do anything else.

About Me

I'm Ruby Developer working in the Bay Area with previous experience in C#, and Java. I have a particular interest in software design principles, and metaprogramming.

  • kavinderd@gmail.com
  • RSS Feed
comments powered by Disqus