#! /usr/bin/env python

## Copyright 2000-2001 by LivingLogic AG, Bayreuth, Germany.
## Copyright 2000-2001 by Walter Dörwald
##
## All Rights Reserved
##
## Permission to use, copy, modify, and distribute this software and its documentation
## for any purpose and without fee is hereby granted, provided that the above copyright
## notice appears in all copies and that both that copyright notice and this permission
## notice appear in supporting documentation, and that the name of LivingLogic AG or
## the author not be used in advertising or publicity pertaining to distribution of the
## software without specific, written prior permission.
##
## LIVINGLOGIC AG AND THE AUTHOR DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
## INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
## LIVINGLOGIC AG OR THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
## DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
## IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
## IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

r"""
<par>ANSIStyle is a module that adds &ansi; color capability to an output stream.</par>

<par>This is done by wrapping an output stream in a Stream object:
<programlisting>
import sys
import ansistyle

s = ansistyle.Stream(sys.stdout)
</programlisting>
</par>

<par>In addition to the <code>write</code> method that writes to the underlying
stream, <code>ansistyle.Stream</code> has three methods for handling color.
<code>pushColor</code> takes an integer argument between 0 and 127. The values 0-127
are equivalent to the following escape sequences:
<ul>
<li>color 0: <code>"\033[30m"</code></li>
<li>...</li>
<li>color 7: <code>"\033[37m"</code></li>
<li>color 8: <code>"\033[1;30m"</code></li>
<li>...</li>
<li>color 15: <code>"\033[1;37m"</code></li>
<li>color 16: <code>"\033[40m"</code></li>
<li>...</li>
<li>color 31: <code>"\033[40;1;37m"</code></li>
<li>...</li>
<li>color 32: <code>"\033[41;30m"</code></li>
<li>...</li>
<li>color 127: <code>"\033[47;1;37m"</code></li>
</ul>
The simplest way to specify a color is in hexadecimal, the high nybble
is the background color, the low nybble the forground color.
</par>

<par>After calling <code>pushColor</code> you can write to the stream with 
<code>write</code>, and the output will appear in that color. Returning
to the previously active color is possible with <code>popColor</code>:
<programlisting>
s.write("spam")
s.pushColor(0x7D)
s.write("eggs") # will be in foreground color 13, background color 7
s.popColor()
</programlisting>
<code>popColor</code> returns the color that was active before the call.
The result of the above statements will look like this:
<programlisting>
<span style="color: #000;">spam<span style="color: #f0f; background-color: #000;">eggs</span></span>
</programlisting>
</par>

<par>As the name suggests, calls to <code>pushColor</code> and <code>popColor</code>
can be <z>nested</z>.</par>

<par>Note that the &ansi; escape sequences won't be written
to the stream immediately when calling <code>pushColor</code>,
but only when output is written to the stream. This
means that the final call to <code>popColor</code> in the above
example will not switch back to the default color.
To achieve this, call the method <code>finish</code> at the end
of all your write statements:
<programlisting>
s.pushColor(0x07)
s.write("spam")
s.pushColor(0x0D)
s.write("eggs")
s.popColor()
s.popColor()
s.finish()
</programlisting>
</par>

<h2>A simpler &api;</h2>

<par>There is a second level &api; for <code>ansistyle.Stream</code>.
The class <code>ansistyle.Text</code> is a <z>colored text sequence</z>
and can be written to an <code>ansistyle.Stream</code> as well.</par>

<par>The color of a <code>Text</code> object is available via the method
<code>getColor</code>. <code>Text.getColor</code> returns the value of the
class attribute <code>color</code>, so derived classes can either
use their own color attribute, or completely overwrite
<code>getColor</code>. ANSIStyle contains classes for all the 16 foreground
colors named <code>Text0</code>, ..., <code>Text15</code>.</par>

<par>In addition to the value <code>0</code>-<code>127</code>, <code>-1</code>
is allowed as a return value for <code>getColor</code>. This means
<z>do not change the current color</z>.</par>

<par>A <code>Text</code> object is a sequence that may consist of strings and
other <code>Text</code> objects and can be constructed simply by passing the
contained items to the constructor. So the above example can be written as:
<programlisting>
s.write(Text7("spam", Text13("eggs")))
s.finish()
</programlisting>
</par>

<par><code>Text</code> objects have a <code>__str__</code> method that can be
used directly to print the object (the call to <code>finish</code> is included).
So the above example can be written as follows:
<programlisting>
print Text7("spam", Text13("eggs"))
</programlisting>
</par>
"""

__version__ = tuple(map(int, "$Revision: 1.18 $"[11:-2].split(".")))
# $Source: /data/cvsroot/LivingLogic/ansistyle/ansistyle.py,v $

import types

from _ansistyle import Stream

class StringBuffer:
	def __init__(self):
		self.buffer = []
	def write(self, text):
		self.buffer.append(text)

class Text:
	"""
	a colored string, colors can be numbers from
	0 to 15 for the 16 ANSI colors or -1 if this
	Text should use the current color. A Text object
	is a sequence, the sequence items may either be
	strings or Text objects themselves.
	"""
	color = -1 # the default color, which means "do not switch color"

	def __init__(self, *content):
		self.content = list(content)

	def __getitem__(self, index):
		return self.content[index]

	def __setitem__(self, index, value):
		self.content[index] = value

	def __delitem__(self, index):
		del self.content[index]

	def __getslice__(self, index1, index2):
		return self.content[index1:index2]

	def __setslice__(self, index1, index2, sequence):
		self.content[index1:index2] = sequence

	def __delslice__(self, index1, index2):
		del self.content[index1:index2]

	def __len__(self):
		return len(self.content)

	def append(self, *others):
		self.content.extend(others)

	def insert(self, index, *others):
		self.content[index:index] = others

	def getColor(self):
		"""
		return the color of this string. Overwrite this method
		or the class attribute color to change the behaviour.
		"""
		return self.color

	def __repr__(self):
		return "%s(%s)" % (self.__class__.__name__, ", ".join([ repr(text) for text in self.content ]))

	def __str__(self):
		"""
		return the resulting string with ANSI color escape sequences.
		"""
		stream = Stream(StringBuffer())
		stream.write(self)
		stream.finish() # return to the default color
		return "".join(stream.stream.buffer)

class Text0(Text):
	color = 0

class Text1(Text):
	color = 1

class Text2(Text):
	color = 2

class Text3(Text):
	color = 3

class Text4(Text):
	color = 4

class Text5(Text):
	color = 5

class Text6(Text):
	color = 6

class Text7(Text):
	color = 7

class Text8(Text):
	color = 8

class Text9(Text):
	color = 9

class Text10(Text):
	color = 10

class Text11(Text):
	color = 11

class Text12(Text):
	color = 12

class Text13(Text):
	color = 13

class Text14(Text):
	color = 14

class Text15(Text):
	color = 15

"""
"""
class EscapedText(Text):
	"""
	An extension to the text class. Special characters can be replaced
	by customized Text objects via the escapeChar method.
	"""
	def __init__(self, *data):
		newcontent = []
		st = types.StringType
		ut = types.UnicodeType
		for text in data:
			if isinstance(text, Text):
				newcontent.append(text)
			else:
				lasttype = None
				for c in text:
					c = self.escapeChar(c)
					nowtype = type(c)
					if lasttype is nowtype and (nowtype is st or nowtype is ut):
						newcontent[-1] += c
					else:
						newcontent.append(c)
					lasttype = nowtype
		self.content = newcontent # bypass the super class constructor here, because this is faster.

	def escapeChar(self, char):
		"""
		return a replacement Text object for the character char
		or char itself, if the character should be used as is.
		This method should be overwritten by subclasses.
		"""
		return char
