# AnsiString (#185)

**URL:** <https://www.ruby-forum.com/t/ansistring-185/154974>\
**Category:** Ruby\
**Created:** [December 5, 2008, 7:10pm UTC](https://www.ruby-forum.com/t/ansistring-185/154974 "2008-12-05T19:10:08Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![mansfiem](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/m/fbc32d/32.png) [@mansfiem](https://www.ruby-forum.com/u/mansfiem)\
**Post date:** [December 5, 2008, 7:10pm UTC](https://www.ruby-forum.com/t/ansistring-185/154974/1 "2008-12-05T19:10:08Z")

</div>

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

The three rules of Ruby Q. 2:

1. Please do not post any solutions or spoiler discussion for this  
quiz until 48 hours have passed from the time on this message.

2. Support Ruby Q. 2 by submitting ideas as often as you can!  
Visit [http://splatbang.com/rubyquiz/](http://splatbang.com/rubyquiz/).

3. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem  
helps everyone on Ruby T. follow the discussion. Please reply to  
the original quiz message, if you can.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

## AnsiString (#185)

_Quiz description provided by Transfire_

Make a subclass of String (or delegate) that tracks “embedded” ANSI  
codes along with the text. The class should add methods for wrapping  
the text in ANSI codes. Implement as much of the core String API as  
possible. So for example:

```
s1 = AnsiString.new("Hi")
s2 = AnsiString.new("there!)

s1.red # wrap text in red/escape ANSI codes
s1.blue # wrap text in blue/escape ANSI codes

s3 = s1 + ' ' + s2 #=> New AnsiString
s3.to_str #=> "\e[31mHi\e[0m \e[34mthere!\e[0m"

```

I have an [ANSICode](http://facets.rubyforge.org/doc/api/more/classes/ANSICode.html) module (it’s in [Facets](http://facets.rubyforge.org/)) that you are  
welcome to provide for the ANSI backend, if desired. It is easy enough  
to use; the literal equivalent of the above would be:

```
ANSICode.red('Hi') + ' ' + ANSICode.blue('there!')

```

Bonus points for being able to use ANSIStrings in a gsub block:

```
ansi_string.gsub(pattern){ |s| s.red }

```

---

<div class="post-metadata">

**Author:** ![mansfiem](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/m/fbc32d/32.png) [@mansfiem](https://www.ruby-forum.com/u/mansfiem)\
**Post date:** [December 5, 2008, 8:29pm UTC](https://www.ruby-forum.com/t/ansistring-185/154974/2 "2008-12-05T20:29:53Z")

</div>

Since the Facets ANSICode was mentioned, I just thought that I’d also  
make everyone aware of the term-ansicolor gem by Florian F.  
removed\_email\_address@domain.invalid  
which does this type of thing for colors.

[http://term-ansicolor.rubyforge.org/](http://term-ansicolor.rubyforge.org/)  
gem install term-ansicolor

-Rob

Rob B. [http://agileconsultingllc.com](http://agileconsultingllc.com)  
removed\_email\_address@domain.invalid

---

<div class="post-metadata">

**Author:** ![mansfiem](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/m/fbc32d/32.png) [@mansfiem](https://www.ruby-forum.com/u/mansfiem)\
**Post date:** [December 5, 2008, 8:47pm UTC](https://www.ruby-forum.com/t/ansistring-185/154974/3 "2008-12-05T20:47:28Z")

</div>

On Dec 5, 2008, at 1:23 PM, Rob B. wrote:

> Since the Facets ANSICode was mentioned, I just thought that I’d  
> also make everyone aware of the term-ansicolor gem by Florian F. removed\_email\_address@domain.invalid  
> which does this type of thing for colors.

HighLine also does this:

> > require “highline/import”  
> > =\> true
> 
> > say("\<%= color ‘I want to be red!’, :red %\>")  
> > I want to be red!  
> > =\> nil

You can use color() without printing with code like:

red\_str = HighLine.new.color(“I want to be red!”, :red)

And if you use that you can drop the /import in the require.

James Edward G. II

---

<div class="post-metadata">

**Author:** ![mansfiem](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/m/fbc32d/32.png) [@mansfiem](https://www.ruby-forum.com/u/mansfiem)\
**Post date:** [December 5, 2008, 11:01pm UTC](https://www.ruby-forum.com/t/ansistring-185/154974/4 "2008-12-05T23:01:04Z")

</div>

> Make a subclass of String (or delegate) that tracks “embedded” ANSI  
> codes along with the text. The class should add methods for wrapping  
> the text in ANSI codes. Implement as much of the core String API as  
> possible. So for example:
> 
> ```
> s1 = AnsiString.new("Hi")
> s2 = AnsiString.new("there!)
> 
> s1.red # wrap text in red/escape ANSI codes
> s1.blue # wrap text in blue/escape ANSI codes
> 
> ```

Make that ‘s2.blue’.

> ```
> s3 = s1 + ' ' + s2 #=> New AnsiString
> s3.to_str #=> "\e[31mHi\e[0m \e[34mthere!\e[0m"
> 
> ```

I’ve found it pretty challenging to keep the ANSI codes in sync with  
the text while still being able to manipulate the text like a normal  
string. I’m curious to see how other people approach it.

T.

---

<div class="post-metadata">

**Author:** ![mansfiem](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/m/fbc32d/32.png) [@mansfiem](https://www.ruby-forum.com/u/mansfiem)\
**Post date:** [December 7, 2008, 8:49pm UTC](https://www.ruby-forum.com/t/ansistring-185/154974/5 "2008-12-07T20:49:58Z")

</div>

This was a nice quiz, maybe not too challenging, but as Tom put it  
correctly the different codes can easily get mangled up. I have found  
a solution that seems to handle the few testcases well, we will see if  
it pleases ;).  
My additional challenge was to make my solution work with HighLine,  
Facets and Term::Ansicolor, and that is what I did :).

The solution can be found here too: [http://pastie.org/333319](http://pastie.org/333319)

And to test all libraries simply type

> for i in term/ansicolor facets/ansicode highline; do ruby1.9  
> rd-185-sol.rb -f $i; done

> ruby1.9 rd-185-sol.rb

runs, transparently, with a random library of all registered

HYLI

Robert

P.S.  
I do not recall ever have answered 3 RQ in a row before 😛

---

<div class="post-metadata">

**Author:** ![mansfiem](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/m/fbc32d/32.png) [@mansfiem](https://www.ruby-forum.com/u/mansfiem)\
**Post date:** [December 11, 2008, 5:28pm UTC](https://www.ruby-forum.com/t/ansistring-185/154974/6 "2008-12-11T17:28:41Z")

</div>

> ## AnsiString (#185)

Summary and new quiz tomorrow.

---

<div class="post-metadata">

**Author:** ![mansfiem](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/m/fbc32d/32.png) [@mansfiem](https://www.ruby-forum.com/u/mansfiem)\
**Post date:** [December 12, 2008, 7:13pm UTC](https://www.ruby-forum.com/t/ansistring-185/154974/7 "2008-12-12T19:13:45Z")

</div>

It would seem that writing Transfire’s desired `ANSIString` class is  
more difficult that it appears. (Or, perhaps, y’all are busy preparing  
for the holidays.) The sole submission for this quiz comes from  
_Robert D._; it’s not completely to specification nor handles the  
bonus, but it is a good start. (More appropriately, it might be better  
to say that the specification isn’t entirely clear, and that Robert’s  
implementation didn’t match _my_ interpretation of the spec; a proper  
`ANSIString` module would need to provide more details on a number of  
things.)

Robert relies on other libraries to provide the actual ANSI codes;  
seeing as there are at least three libraries that do, Robert provides  
a mechanism to choose between them based on user request and/or  
availability. Let’s take a quick look at this mechanism. (Since this  
quiz doesn’t use the Module mechanism in Robert’s `register_lib`  
routine, I’ve removed the related references for clarity. I suspect  
those are for a larger set of library management routines.)

@use\_lib =  
( ARGV.first == ‘-f’ || ARGV.first == ‘–force’ ) &&  
ARGV[1]

```
 def register_lib lib_path, &blk
   return if @use_lib && lib_path != @use_lib
   require lib_path
   Libraries[lib_path] = blk
 end

 register_lib "facets/ansicode" do | color |
   ANSICode.send color
 end

```

# similar register\_lib calls for “highline” and “term/ansicolor”

class ANSIString  
used\_lib\_name = Libraries.keys[rand( Libraries.keys.size )]  
lib = Libraries[used\_lib\_name]  
case lib  
when Proc  
define\_method : **color** , &lib  
else  
raise RuntimeError, “Nooooo I have explained exactly how to  
register libraries, has I not?”  
end

```
# ... rest of ANSIString ...

```

end

First, we check if the user has requested (via `--force`) a particular  
library. This is used in the first line of `register_lib`, which exits  
early if we try to register a library other than the one specified.  
Then `register_lib` loads the matching library (or all if the user did  
not specify) via `require` as is typical. Finally, a reference to the  
provided code block is kept, indexed by the library name.

This seems, perhaps, part of a larger set of library management  
routines; its use in this quiz is rather simple, as can be seen in the  
calls to `register_lib` immediately following. While registering  
“facets/ansicode”, a block is provided to call `ANSICode.send color`.  
This is then used below in `ANSIString`, when we choose one of the  
libraries to use, recall the corresponding code block, and define a  
new method ` __color__ ` that calls that code block.

Altogether, this is a reasonable technique for putting a façade around  
similar functionality in different libraries and choosing between  
available libraries, perhaps if one or another is not available. It  
seems to me that such library management – at least the general  
mechanisms – might be worthy of its own gem.

Given that we now have a way to access ANSI codes via  
`ANSIString# __color__ `, let’s now move onto the code related to the  
task, starting with initialization and conversion to `String`:

class ANSIString  
ANSIEnd = “\e[0m”

```
def initialize *strings
  @strings = strings.dup
end

def to_s
  @strings.map do |s|
    case s
  when String
    s
  when :end
    ANSIEnd
  else
    __color__ s
  end
end.join
   end

```

end

Internally, `ANSIString` keeps an array of strings, its initial value  
set to a copy of the initialization parameters. So we can create ANSI  
string objects in a couple of ways:

s1 = ANSIString.new “Hello, world!”  
s2 = ANSIString.new :green, "Merry ", :red, “Christmas!”, :end

When converting with `to_s`, each member of that array is  
appropriately converted to a `String`. It is assumed that members of  
the array are either already `String` objects (so are mapped to  
themselves), the `:end` symbol (so mapped to constant string  
`ANSIEnd`), or appropriate color symbols available in the previously  
loaded library (mapped to the corresponding ANSI string available  
through method ` __color__ `). Once all items in the array are converted  
to strings, a simple call to `join` binds them together into one,  
final string.

Let’s look at string concatenation:

class ANSIString  
def + other  
other.add\_reverse self  
rescue NoMethodError  
self.class::new( \*( **end** \<\< other ) )  
end

```
def add_reverse an_ansi_str
  self.class::new( *(
    an_ansi_str.send( : __end__ ) + __end__
  ) )
end

private
def __end__
  @strings.reverse.find{ |x| Symbol === x} == :end ?
    @strings.dup : @strings.dup << :end
end

```

end

Before we get to the concatenation itself, take a quick look at helper  
method ` __end__ `. It looks for the last symbol and compares it against  
`:end`. Whether true or false, the `@string` array is duplicated (and  
so protects the instance variable from change). Only, ` __end__ ` does  
not append another `:end` symbol if unnecessary.

I was a little confused, at first, about the implementation of  
`ANSIString` concatenation. Perhaps Robert had other plans in mind,  
but it seemed to me this work could be simplified. Since `add_reverse`  
is called nowhere else (and I couldn’t imagine it being called by the  
user, despite the public interface), I tried inserting `add_reverse`  
inline to `+` (fixing names along the way):

def + other  
other.class::new( \*(self.send(: **end** ) + other. **end** ) )  
rescue NoMethodError  
self.class::new( \*( **end** \<\< other ) )  
end

And, with further simplification:

def + other  
other.class::new( \*( **end** + other.send(: **end** ) ) )  
rescue NoMethodError  
self.class::new( \*( **end** \<\< other ) )  
end

I believed Robert had a bug, neglecting to call ` __end__ ` in the  
second case, until I realized my mistake: `other` is not necessarily  
of the `ANSIString` class, and so would not have the ` __end__ ` method.  
My attempt to fix my mistake was to rewrite again as this:

def + other  
ANSIString::new( \*( **end** + other.to\_s ) )  
end

But that has its own problems if `other` _is_ an `ANSIString`; it  
neglects to end the string and converts it to a simple `String` rather  
than maintaining its components. Clearly undesirable. Obviously,  
Robert’s implementation is the right way… or is it? Going back to  
this version:

def + other  
other.class::new( \*( **end** + other.send(: **end** ) ) )  
rescue NoMethodError  
self.class::new( \*( **end** \<\< other ) )  
end

Ignoring the redundancy, this actually works. My simplification will  
throw the `NoMethodError` exception, because `String` does not define  
` __end__ `, just as Robert’s version throws that exception if either  
`add_reverse` or ` __end__ ` is not defined. So, removing redundancy, I  
believe concatenation can be simplified correctly as:

def + other  
self.class::new( \*(  
**end** + (other.send(: **end** ) rescue [other] )  
) )  
end

For me, this reduces concatenation to something more quickly  
understandable.

One last point on concatenation; Robert’s version will create an  
object of class `other.class` if that class has both methods  
`add_reverse` and ` __end__ `, whereas my simplification does not.  
However, it seems unlikely to me that any class other than  
`ANSIString` will have those methods. I recognize that my assumption  
here may be flawed; Robert will have to provide further details on his  
reasoning or other uses of the code.

Finally, we deal with adding ANSI codes to the ANSI strings (aside  
from at initialization):

class ANSIString  
def end  
self.class::new( \* **end** )  
end

```
def method_missing name, *args, &blk
  super( name, *args, &blk ) unless args.empty? && blk.nil?
  class << self; self end.module_eval do
    define_method name do
    self.class::new( *([name.to_sym] + @strings).flatten )
    end
  end
  send name
end

```

end

Method `end` simply appends the symbol `:end` to the `@strings` array  
by making use of the existing ` __end__ ` method. Reusing ` __end__ ` (as  
opposed to just doing `@strings << :end`) ensures that we don’t have  
unnecessary `:end` symbols in the string.

Finally, `method_missing` catches all other calls, such as `bold` or  
`red`. Any calls with arguments or a code block are passed up first to  
the superclass, though considering the parent class is `Object`, any  
such call is likely to generate a `NoMethodError` exception (since, if  
the method was in `Object`, `method_missing` would not have been  
called). Also note that whether “handled” by the superclass or not,  
all missing methods are _also_ handled by the rest of the code in  
`method_missing`. I don’t know if that is intentional or accidental.  
In general, this seems prone to error, and it would seem a better  
tactic either to discern the ANSI code methods from the loaded module  
or to be explicit about such codes.

In any case, calling `red` on `ANSIString` the first time actually  
generates a new method, by way of the `define_method` call located in  
`method_missing`. Further calls to `red` (and the first call, via the  
last line `send name`) will actually use that new method, which  
prepends `red.to_sym` (that is, `:red`) to the string in question.

At this point, `ANSIString` handles basic initialization,  
concatenation, ANSI codes and output; it does not handle the rest of  
the capabilities of `String` (such as substrings, `gsub`, and others),  
so it is not a drop-in replacement for strings. I believe it could be,  
with time and effort, but that is certainly a greater challenge than  
is usually attempted on Ruby Q…

---

<div class="post-metadata">

**Author:** ![mansfiem](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/m/fbc32d/32.png) [@mansfiem](https://www.ruby-forum.com/u/mansfiem)\
**Post date:** [December 12, 2008, 9:34pm UTC](https://www.ruby-forum.com/t/ansistring-185/154974/8 "2008-12-12T21:34:06Z")

</div>

On Fri, Dec 12, 2008 at 7:06 PM, Matthew M. [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid) wrote:  
Since this quiz doesn’t use the Module

> mechanism in Robert’s `register_lib` routine, I’ve removed the related  
> references for clarity. I suspect those are for a larger set of library  
> management routines.)  
> Exactly, it was the multi library approach which interested me more  
> than the ANSIString implementation, hence  
> the sloppy implementation :(.  
>   
> def add\_reverse an\_ansi\_str  
> end  
> (and I couldn’t imagine it being called by the user, despite the public
> 
> ```
> other.class::new( *( __end__ + other.send(: __end__ ) ) )
> def + other
> 
> ```
> 
> me that any class other than `ANSIString` will have those methods. I  
> recognize that my assumption here may be flawed; Robert will have to provide  
> further details on his reasoning or other uses of the code.  
> Not really I was quite sloppy, it took me same time to re-understand  
> my code, always a bad sign.  
> Sorry for giving you so much work :(.

Cheers R.

---

<div class="post-metadata">

**Author:** ![mansfiem](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/m/fbc32d/32.png) [@mansfiem](https://www.ruby-forum.com/u/mansfiem)\
**Post date:** [December 31, 2008, 4:15am UTC](https://www.ruby-forum.com/t/ansistring-185/154974/9 "2008-12-31T04:15:25Z")

</div>

On Dec 12, 1:06 pm, Matthew M. [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid) wrote:

> It would seem that writing Transfire’s desired `ANSIString` class is  
> more difficult that it appears. (Or, perhaps, y’all are busy preparing  
> for the holidays.)

Yep. That’s too bad. The holidays have had me occupied as well, and it  
is a tricky problem. I was hoping someone brighter than I would come  
up with a really clever way of doing it.

Robert’s implementation using an array is string and symbol is much  
like my first stab at it. In some ways I think maybe it’s the better  
way to go, although more limited in scope, one can at least wrap ones  
head around the implementation without too much trouble. Thanks for  
taking a stab at the quiz Robert!

My implementation on the other hand tries to go the full nine-yards  
toward drop-in compatibility with the core String class. It’s not  
complete by any means, but the foundation is in place for doing so,  
ie. the #shift\_marks method. The downside though is that the algorithm  
is somewhat complex and, worse still, time consuming, not to mention  
imperfect – hence my hope someone else might have a brighter idea for  
going about this.

require ‘facets/ansicode’  
require ‘facets/string’

# ANSIStrings stores a regular string (@text) and

# a Hash mapping character index to ansicodes (@marks).

# For example is we has the string:

# 

# “Big Apple”

# 

# And applied the color red to it, the marks hash would be:

# 

# { 0=\>[:red] , 9=\>[:clear] }

# 

class ANSIString

```
CLR = ANSICode.clear

attr :text
attr :marks

def initialize(text=nil, marks=nil)
  @text = (text || '').to_s
  @marks = marks || []
  yield(self) if block_given?
end

def to_s
  s = text.dup
  m = marks.sort do |(a,b)|
    v = b[0] <=> a[0]
    if v == 0
      (b[1] == :clear or b[1] == :reset) ? -1 : 1
    else
      v
    end
  end
  m.each do |(index, code)|
    s.insert(index, ANSICode. __send__ (code))
  end
  #s << CLR unless s =~ /#{Regexp.escape(CLR)}$/ # always end

```

with a clear  
s  
end

```
#
alias_method :to_str, :to_s

def size ; text.size ; end

def upcase ; self.class.new(text.upcase, marks) ; end
def upcase! ; text.upcase! ; end

def downcase ; self.class.new(text.upcase, marks) ; end
def downcase! ; text.upcase! ; end

def +(other)
  case other
  when String
    ntext = text + other.text
    nmarks = marks.dup
    omarks = shift_marks(0, text.size, other.marks)
    omarks.each{ |(i, c)| nmarks << [i,c] }
  else
    ntext = text + other.to_s
    nmarks = marks.dup
  end
  self.class.new(ntext, nmarks)
end

def slice(*args)
  if args.size == 2
    index, len = *args
    endex = index+len
    new_text = text[index, len]
    new_marks = []
    marks.each do |(i, v)|
      new_marks << [i, v] if i >= index && i < endex
    end
    self.class.new(new_text, new_marks)
  elsif args.size == 1
    rng = args.first
    case rng
    when Range
      index, endex = rng.begin, rng.end
      new_text = text[rng]
      new_marks = []
      marks.each do |(i, v)|
        new_marks << [i, v] if i >= index && i < endex
      end
      self.class.new(new_text, new_marks)
    else
      nm = marks.select do |(i,c)|
        marks[0] == rng or ( marks[0] == rng + 1 &&

```

[:clear, :reset].include?(marks[1]) )  
end  
self.class.new(text[rng,1], nm)  
end  
else  
raise ArgumentError  
end  
end

```
alias_method :[], :slice

# This is more limited than the normal String method.
# It does not yet support a block, and +replacement+
# won't substitute for \1, \2, etc.
#
# TODO: block support.
def sub!(pattern, replacement=nil, &block)
  mark_changes = []
  text = @text.sub(pattern) do |s|
    index = $~.begin(0)
    replacement = block.call(s) if block_given?
    delta = (replacement.size - s.size)
    mark_changes << [index, delta]
    replacement
  end
  marks = @marks
  mark_changes.each do |index, delta|
    marks = shift_marks(index, delta, marks)
  end
  @text = text
  @marks = marks
  self
end

#
def sub(pattern,replacement=nil, &block)
  dup.sub!(pattern, replacement, &block)
end

#
def gsub!(pattern, replacement=nil, &block)
  mark_changes = []
  mark_additions = []
  text = @text.gsub(pattern) do |s|
    index = $~.begin(0)
    replacement = block.call(self.class.new(s)) if block_given?
    if self.class===replacement
      adj_marks = replacement.marks.map{ |(i,c)| [i+index,c] }
      mark_additions.concat(adj_marks)
      replacement = replacement.text
    end
    delta = (replacement.size - s.size)
    mark_changes << [index, delta]
    replacement
  end
  marks = @marks
  mark_changes.each do |(index, delta)|
    marks = shift_marks(index, delta, marks)
  end
  marks.concat(mark_additions)
  @text = text
  @marks = marks
  self
end

#
def gsub(pattern, replacement=nil, &block)
  dup.gsub!(pattern, replacement, &block)
end

#
def ansi(code)
  m = marks.dup
  m.unshift([0, code])
  m.push([size, :clear])
  self.class.new(text, m)
end
alias_method :color, :ansi

#
def ansi!(code)
  marks.unshift([0, ansicolor])
  marks.push([size, :clear])
end
alias_method :color!, :ansi!

def red ; color(:red) ; end
def green ; color(:green) ; end
def blue ; color(:blue) ; end
def black ; color(:black) ; end
def magenta ; color(:magenta) ; end
def yellow ; color(:yellow) ; end
def cyan ; color(:cyan) ; end

def bold ; ansi(:bold) ; end
def underline ; ansi(:underline) ; end

def red! ; color!(:red) ; end
def green! ; color!(:green) ; end
def blue! ; color!(:blue) ; end
def black! ; color!(:black) ; end
def magenta! ; color!(:magenta) ; end
def yellow! ; color!(:yellow) ; end
def cyan! ; color!(:cyan) ; end

def bold! ; ansi!(:bold) ; end
def underline! ; ansi!(:underline) ; end

```

private

```
#
def shift_marks(index, delta, marks=nil)
  new_marks = []
  (marks || @marks).each do |(i, c)|
    case i <=> index
    when -1
      new_marks << [i, c]
    when 0, 1
      new_marks << [i+delta, c]
    end
  end
  new_marks
end

#
def shift_marks!(index, delta)
 @marks.replace(shift_marks(index, delta))
end

```

end

Sorry for my late post. I’m only now starting to get settled back into  
the routine of things.
