ANSI Strings (2nd Edition)

Posted by Mathew Abonyi Wed, 07 Feb 2007 06:32:22 GMT

I had a couple of instances where I was adding to the ANSI_CODES constant and creating all sorts of ugly errors. Then a script of mine whispered in my ear its desire for meta-aliases. That prompted a small rewrite of the library.

Ansi codes are now dynamically defined and more easily manipulated without overcomplicating things. Also, I added an RSpec test to the repository for my own piece of mind and to help with refactoring a few things. I don’t know, should I give it a version number? Nah. Just ‘Second Edition’. I don’t envisage any other serious revisions to it any more. What I changed:

  • dumped the array structure for ansi codes
  • added a few non-colour ANSI codes
  • dynamic ansi code definitions: String.define_ansi :alias => “123m”
  • meta-aliases: String.define_ansi :clear_both => [:clear_line, :clear_screen]
  • String#method definitions only if it doesn’t exist already
  • String#method definitions on the fly
  • spec test

And here’s the code:

$ANSI ||= false

class String

  class << self

    attr_accessor :ansi_codes

    def ansi_codes
      @ansi_codes ||= {}
    end

    def define_ansi(definitions = {})
      self.ansi_codes = ansi_codes.merge(definitions)
      define_shortcuts(definitions)
    end

    def define_shortcuts(definitions = {})
      definitions.keys.each do |meth|
        unless instance_methods.include?(meth.to_s)
          define_method(meth) { with_ansi(meth) }
        end
      end
    end

  end

  define_ansi :normal         => "0m",      :bold           => "1m"
  define_ansi :underline      => "4m",      :blink          => "5m"
  define_ansi :reverse_video  => "7m",      :invisible      => "8m"
  define_ansi :black          => "30m",     :red            => "31m"
  define_ansi :green          => "32m",     :yellow         => "33m"
  define_ansi :blue           => "34m",     :magenta        => "35m"
  define_ansi :cyan           => "36m",     :white          => "37m"
  define_ansi :black_bg       => "40m",     :red_bg         => "41m"
  define_ansi :green_bg       => "42m",     :yellow_bg      => "43m"
  define_ansi :blue_bg        => "44m",     :magenta_bg     => "45m"
  define_ansi :cyan_bg        => "46m",     :white_bg       => "47m"
  define_ansi :clear_line     => "K",       :clear_screen   => "2J"
  define_ansi :go_home        => "0H",      :go_to_end      => "80L"

  # Wrap a string with an arbitrary ansi code and the ansi normal code
  def with_ansi(*codes)
    use_ansi? ? "#{sym_to_ansi(*codes)}#{self}#{sym_to_ansi(:normal)}" : self
  end

  # Just a little metaprogramming shortcut
  def ansi_codes; self.class.ansi_codes; end

  private
  def sym_to_ansi(*symbols)
    symbols.inject("") do |string, symbol|
      if code = ansi_codes[symbol]
        string << (code.is_a?(Array) ? sym_to_ansi(*code) : "\e[#{code}")
      end
      string
    end
  end

  # determine whether we have ansi support or ANSI enabled
  def use_ansi?
    $ANSI && RUBY_PLATFORM !~ /win32/i
  end

end

You can grab the new version from my ‘other’ directory at Google Code:

  svn co http://mabs29.googlecode.com/svn/trunk/other/ansi_strings .

Posted in  | no comments | no trackbacks

ANSI Strings: your fun-loving, colourful pal (quick-fix colour library)

Posted by Mathew Abonyi Thu, 01 Feb 2007 07:54:08 GMT

You can make your command line interface look a little better than just a bungle of white on black (or however you or your users set your terminal). In my on-going development of a few little open source projects, all of them with CLIs, I abstracted this cute addition to the Ruby snippets junkyard and decided to air it.

With it, you can wrap any string with ANSI codes like this:

  "I am going to be bold and cyan!".bold.cyan
  "I am going to be underlined. YAY!".underline
  "Oh, for the love of God... please STOP BLINKING!".blink

Make your command line interface glisten with choice and sparing use of ANSI codes not just with the above methods dynamically added to the String class, but also by using with_ansi(*ansi_aliases). It’s really very simple. Also, all application of ANSI is dependent on $ANSI being true, which, by default, is false (as you can at the bottom of this article, in the first line of code). Lastly, ANSI Strings checks the current platform and won’t be applied to strings on a win32 platform—I have no way of testing whether win32 can in certain instances show colour, so I just disable it.

You can download ANSI Strings from my googlecode repository like so:

 svn co http://mabs29.googlecode.com/svn/trunk/other/ansi_strings

Or you can just look at it here, since it is so simple and small:

$ANSI ||= true

class String

  ANSI_CODES = {
    :normal         => [0, "m"],
    :bold           => [1, "m"],
    :underline      => [4, "m"],
    :blink          => [5, "m"],
    :reverse_video  => [7, "m"],
    :invisible      => [8, "m"],

    :black          => [30, "m"],
    :red            => [31, "m"],
    :green          => [32, "m"],
    :yellow         => [33, "m"],
    :blue           => [34, "m"],
    :magenta        => [35, "m"],
    :cyan           => [36, "m"],
    :white          => [37, "m"],

    :black_bg       => [40, "m"],
    :red_bg         => [41, "m"],
    :green_bg       => [42, "m"],
    :yellow_bg      => [43, "m"],
    :blue_bg        => [44, "m"],
    :magenta_bg     => [45, "m"],
    :cyan_bg        => [46, "m"],
    :white_bg       => [47, "m"]
  }

  # Create shortcuts to wrap any string with ansi codes
  ANSI_CODES.keys.each { |meth| define_method(meth) { with_ansi(meth) } }

  # Wrap a string with an arbitrary ansi code and the ansi normal code
  def with_ansi(*codes)
    use_ansi? ? "#{sym_to_ansi(*codes)}#{self}#{sym_to_ansi(:normal)}" : self
  end

  private
  def sym_to_ansi(*symbols)
    symbols.inject("") do |string, symbol|
      string << "\033[%s%s" % ANSI_CODES[symbol] if ANSI_CODES[symbol]; string
    end
  end

  # determine whether we have ansi support or ANSI enabled
  def use_ansi?
    $ANSI && RUBY_PLATFORM !~ /win32/i
  end

end

Posted in ,  | 2 comments | 1 trackback

Older posts: 1 2