ColourText
.

![alt text](http://25.media.tumblr.com/tumblr_lxlteprWtD1r26npvo1_500.png)

**Purpose**. This is a simple pattern that shows how to output colour in a
console. If you run the tool, you can see test cases run, much like in the
screenshot above this post.

**Usage**. To get blue text like you see in the example, you'd create an
object called cstring like this:

```python
ob = ColourText.cstring("This has |blue:blue* text")
```

Then render it:

```python
ColourText.to_ascii(ob)
```

**Code.**
```python
#!/usr/bin/python
#
# Provides a mechanism for creating text with arbitrary formatting.
#
# Example
#   
#   raw = "|red:this text comes out red* Now we're outside of formatting"
#   ob = cstring(raw)
#   print to_ascii(ob)
#
# Backslash acts as an escape character.
#
# There's a simple to_ascii function further down that returns a stream
# of symbols that will render to colour in ascii, and there's test
# cases at the end showing more examples
#
# cturner, 20101121
#

class cstring(object):
    """Colour string. Star marks end of a block.
    Example: "Text |red:I am red|blue:I'm blue* This bit has no formatting"."""
    # Indicates a string
    MARKER_S = 's'
    # Indicates start of a format block
    MARKER_F = 'f'
    # Indicates end of a format block
    MARKER_X = 'x'
    def __init__(self, text):
        (self._sequence, self._length) = self._parse(text)
    def _parse(self, text):
        """Returns a sequence of segments, and a length."""
        cb = []
        wb = []

        _str = 0
        _tag = 1
        _str = 2
        b_escape = False
        state = _str
        length = 0
        for c in text:
            if b_escape:
                wb.append(c)
            else:
                if state == _str:
                    if c == '|':
                        length += len(wb)
                        cb.append( (self.MARKER_S, ''.join(wb)) )
                        wb = []
                        state = _tag
                    elif c == '*':
                        length += len(wb)
                        cb.append( (self.MARKER_S, ''.join(wb)) )
                        cb.append( (self.MARKER_X, '') )
                        wb = []
                    else:
                        wb.append(c)
                else:
                    if c == ':':
                        format_option = ''.join(wb)
                        cb.append( (self.MARKER_F, format_option) )
                        wb = []
                        state = _str
                    else:
                        wb.append(c)
        if state != _str:
            er = ' '.join( [ "Invalid format string. Still in format option"
                           , "at end of string. '%s'"%(''.join(wb))
                           ] )
            raise Exception(er)
        if 0 < len(wb):
            length += len(wb)
            cb.append( (self.MARKER_S, ''.join(wb)) )
            if not cb[-1][0] == self.MARKER_X:
                cb.append( (self.MARKER_X, '') )
        return (cb, length)
    def __iter__(self):
        return (x for x in self._sequence)
    def __len__(self):
        return self._length

class CstringException(Exception):
    def __init__(self, msg):
        self.message = msg


# --------------------------------------------------------
#   struct
# --------------------------------------------------------
COLOURS = { 'blue': '\033[94m'
          , 'green': '\033[92m'
          , 'yellow': '\033[93m'
          , 'red': '\033[91m'
          }
ENDC = '\033[0m'

def to_ascii(cs):
    sb = []
    for code, value in cs:
        if code == 's':
            sb.append(value)
        elif code == 'f':
            if value in COLOURS:
                sb.append(COLOURS[value])
            else:
                er = "Don't have a handler for format option '%s'"%value
                raise Exception(er)
        elif code == 'x':
            sb.append(ENDC)
            sb.append(value)
        else:
            raise Exception("cstring is invalid stream. got code %s."%code)
    return ''.join(sb)


# --------------------------------------------------------
#   test
# --------------------------------------------------------
def test():
    lst_test_string = [ "This is simple text."
                      , u"This is simple text, but unicode"
                      , "This has |blue:blue* text"
                      , u"Unicode |green:green |red:and red* text"
                      , "|yellow:neglect to close tap here"
                      , ''
                      , "That last line was empty deliberately"
                      ]

    for c in COLOURS:
        lst_test_string.append("|%s:%s*"%(c, c))

    for s in lst_test_string:
        print "> %s"%s
        cs = cstring(s)
        #print cs.sequence
        print 'length', len(cs)
        print to_ascii(cs)
        #print 


# --------------------------------------------------------
#   loader
# --------------------------------------------------------
if __name__ == '__main__':
    test()
```