#!/usr/bin/env python
# -*- coding: utf-8 -*-
#--------------------------------------------------------------------
# Filename: termcolors.py
# Author: Mazhugin Aleksey
# Created: 2019/07/25
# ID: $Id: termcolors.py 63 2019-09-09 15:31:40Z Aleksey $
# URL: $URL: file:///D:/svn_/pybag/trunc/src/termcolors.py $
# Copyright: Copyright (c) 2019, Mazhugin Aleksey.
# License: BSD
#--------------------------------------------------------------------
"""
Simple colors for terminal output.
Main class is `TermColors`.
::
ANSY 4 bit color for unix.
==========================
\033[FG;BGm \x1bFG;BGm \e[FG;BGm
30-37 (30 + n) == text 90-97==bright text
40-47 (40 + n) == background 100-107==bright background
n color
0 black
1 red
2 green
3 yellow
4 blue
5 magenta
6 cyan
7 white
\033[31m == set red text
\033[31;42m == set red text and green background
ESC==\\033
ESC [ 0 m # reset all (colors and brightness)
ESC [ 1 m # bright
ESC [ 2 m # dim (looks same as normal brightness)
ESC [ 22 m # normal brightness
# FOREGROUND:
ESC [ 30 m # black
ESC [ 31 m # red
ESC [ 32 m # green
ESC [ 33 m # yellow
ESC [ 34 m # blue
ESC [ 35 m # magenta
ESC [ 36 m # cyan
ESC [ 37 m # white
ESC [ 39 m # reset
# BACKGROUND
ESC [ 40 m # black
ESC [ 41 m # red
ESC [ 42 m # green
ESC [ 43 m # yellow
ESC [ 44 m # blue
ESC [ 45 m # magenta
ESC [ 46 m # cyan
ESC [ 47 m # white
ESC [ 49 m # reset
# cursor positioning
ESC [ y;x H # position cursor at x across, y down
ESC [ y;x f # position cursor at x across, y down
ESC [ n A # move cursor n lines up
ESC [ n B # move cursor n lines down
ESC [ n C # move cursor n characters forward
ESC [ n D # move cursor n characters backward
# clear the screen
ESC [ mode J # clear the screen
# clear the line
ESC [ mode K # clear the line
Windows colors.
===============
0-7 dark
8-15 bright
text background
0 black 0
1 blue 16
2 green 32
3 cyan 48 (blue+green)
4 red 64
5 magenta 80 (blue+red)
6 yellow 96 (green+red)
7 white 112 (blue+green+red)
FOREGROUND_BLUE = 0x01,
FOREGROUND_GREEN = 0x02,
FOREGROUND_RED = 0x04,
FOREGROUND_INTENSITY = 0x08,
BACKGROUND_BLUE = 0x10,
BACKGROUND_GREEN = 0x20,
BACKGROUND_RED = 0x40
BACKGROUND_INTENSITY = 0x80
"""
#----------- Implementation --------------
__docformat__ = "restructuredtext" # "epytext"
import ctypes
import platform
import sys
import re
import atexit
import io
_OSWIN = platform.system().lower().startswith('windows')
"""Is os is Windows."""
class TermColors(object):
"""
Base class for terminal colorization.
Use `getDefault` for get default terminal colorization class for you OS.
"""
C_BLACK = 0
C_BLUE = 1
C_GREEN = 2
C_CYAN = 3
C_RED = 4
C_MAGENTA = 5
C_YELLOW = 6
C_WHITE = 7
C_RE_PATT = "<([rgbkcmyw_RGBKCMYW]{1,2})>((.|\n)*?)\\1>"
"""Pattern for search tags.[1]-color string, [2]-string for colorize."""
@staticmethod
def getDefault(stream=None):
"""
Return TermColors instance with default color scheme (initialized
for current terminal).
"""
r = None
if _OSWIN:
r = TermColorsWindows(stream)
else:
r = TermColorsUnix(stream)
return r
def __init__(self, stream=None):
"""
``stream`` - Stream for out colors. In windows not used.
Default is ``None`` = use ``sys.stdout``.
"""
self._stream = stream
"""stream for out colors."""
self._color_table = {}
"""Color table. Index is a color constants from class, C_xxxx.
"""
self._isbg = False
"""If true then now set bg colors."""
self._txtbright = None
"""Use text bright. If None - use last or default."""
self._bgbright = None
"""Use text bright. If None - use last or default."""
self._ctxt = None
"""Text color. If None - use last or default."""
self._cbg = None
"""Background color. If None - use last or default."""
self._last_ctxt = None
"""Previous text color."""
self._last_cbg = None
"""Previous bg color."""
self._last_bgbright = None
"""Previous bg brightness."""
self._last_txtbright = None
"""Previous text brightness."""
self._reset_value = None
"""Color color value or None for use default."""
self._recomp = re.compile(self.C_RE_PATT)
"""Compiled pattern for RE."""
self._tag_table = {}
"""
Table for match colr strings to color index.
Store 2tuple: (color,bright).
"""
self._tag_table["r"] = (self.C_RED, False)
self._tag_table["g"] = (self.C_GREEN, False)
self._tag_table["b"] = (self.C_BLUE, False)
self._tag_table["k"] = (self.C_BLACK, False)
self._tag_table["c"] = (self.C_CYAN, False)
self._tag_table["m"] = (self.C_MAGENTA, False)
self._tag_table["y"] = (self.C_YELLOW, False)
self._tag_table["w"] = (self.C_WHITE, False)
self._tag_table["_"] = (None, None)
self._tag_table["R"] = (self.C_RED, True)
self._tag_table["G"] = (self.C_GREEN, True)
self._tag_table["B"] = (self.C_BLUE, True)
self._tag_table["K"] = (self.C_BLACK, True)
self._tag_table["C"] = (self.C_CYAN, True)
self._tag_table["M"] = (self.C_MAGENTA, True)
self._tag_table["Y"] = (self.C_YELLOW, True)
self._tag_table["W"] = (self.C_WHITE, True)
def _calcColor(self, text_color, text_bright, bg_color, bg_bright):
"""
Calculate color value.
``text_color`` - Text color, use class color constant C_xxxxx.
If color argument is ``None`` use last value or not use this component if no last.
or use default value. See implementation detail.
``bg_color`` - Background color, use class color constant C_xxxxx.
If color argument is ``None`` use last value or not use this component if no last.
or use default value. See implementation detail.
``text_bright`` - Text brightness,``True`` for bright, ``False`` for dark color.
If color argument is ``None`` use last value or not use this component if no last.
or use default value. See implementation detail.
``bg_bright`` - Background brightness,``True`` for bright, ``False`` for dark color.
If color argument is ``None`` use last value or not use this component if no last.
or use default value. See implementation detail.
Return value for set color. Returned value must be right for use without check.
"""
def _setColor(self, value):
"""
Set specified color from value.
Value must be right. (Not check more).
"""
def _calcResetColor(self):
"""
Calculate value for reset colors to default.
"""
def set_reset_color(self):
"""
Save specified color to use for reset.
"""
v = self._decode_color()
self._reset_value = v
self._clear()
def black(self):
"""
Set black color. Return self instance.
"""
if self._isbg:
self._cbg = self.C_BLACK
else:
self._ctxt = self.C_BLACK
return self
def blue(self):
"""
Set blue color. Return self instance.
"""
if self._isbg:
self._cbg = self.C_BLUE
else:
self._ctxt = self.C_BLUE
return self
def green(self):
"""
Set green color. Return self instance.
"""
if self._isbg:
self._cbg = self.C_GREEN
else:
self._ctxt = self.C_GREEN
return self
def cyan(self):
"""
Set cyan color. Return self instance.
"""
if self._isbg:
self._cbg = self.C_CYAN
else:
self._ctxt = self.C_CYAN
return self
def red(self):
"""
Set red color. Return self instance.
"""
if self._isbg:
self._cbg = self.C_RED
else:
self._ctxt = self.C_RED
return self
def magenta(self):
"""
Set magenta color. Return self instance.
"""
if self._isbg:
self._cbg = self.C_MAGENTA
else:
self._ctxt = self.C_MAGENTA
return self
def yellow(self):
"""
Set yellow color. Return self instance.
"""
if self._isbg:
self._cbg = self.C_YELLOW
else:
self._ctxt = self.C_YELLOW
return self
def white(self):
"""
Set white color. Return self instance.
"""
if self._isbg:
self._cbg = self.C_WHITE
else:
self._ctxt = self.C_WHITE
return self
def bright(self):
"""
Set bright color. Return self instance.
"""
if self._isbg:
self._bgbright = True
else:
self._txtbright = True
return self
def dark(self):
"""
Set dark color. Return self instance.
"""
if self._isbg:
self._bgbright = False
else:
self._txtbright = False
return self
def bg(self):
"""
Go to enter background color. Return self instance.
"""
self._isbg = True
return self
def txt(self):
"""
Go to enter text color(this is default). Return self instance.
"""
self._isbg = False
return self
def set(self):
"""
Set (apply) all specified colors.
"""
v = self._decode_color()
self._setColor(v)
self._clear()
def _decode_color(self):
"""
Decode color to value from specified colors.
"""
t = self._ctxt is None and self._last_ctxt or self._ctxt
bg = self._cbg is None and self._last_cbg or self._cbg
tbr = self._txtbright is None and self._last_txtbright or self._txtbright
bgbr = self._bgbright is None and self._last_bgbright or self._bgbright
v = self._calcColor(t, tbr, bg, bgbr)
return v
def _clear(self, save_last=True):
"""
Clear specified colors to ``None`` and save to last (if not ``None``).
``save_last`` - if true? then save to last value.
"""
if save_last:
if self._ctxt is not None:
self._last_ctxt = self._ctxt
if self._cbg is not None:
self._last_cbg = self._cbg
if self._txtbright is not None:
self._last_txtbright = self._txtbright
if self._bgbright is not None:
self._last_bgbright = self._bgbright
self._ctxt=None
self._cbg=None
self._isbg=False
self._txtbright = None
self._bgbright=None
def reset(self):
"""
Try reset colors to default.
"""
if self._reset_value is None:
self._reset_value = self._calcResetColor()
self._setColor(self._reset_value)
self._clear(False) # do not save reset to last
def write(self, s, skip=False, stream=None):
"""
Colorized ouput string to ``stream``.
If **stream** ``None`` then use initialized `self._stream`,
otherwise use ``sys.stdout``.
**skip** - If True then remove color tags from string and out it
without colorization.
Sintax is next (html-like): ``some text``, where first C is
a text color, second C is a background color.
``color`` is next code:
r - red,
g - green,
b - blue,
k - black,
c - cyan,
m - magenta,
y - yellow,
w - white,
_ - not use (use previous or default).
Lowcase letter mean dark color, Uppercase mean bright color.
For example:
``Bright red on default background.``
``Dark white on black``
"""
if stream is None:
stream = self._stream
if stream is None:
stream = sys.stdout
pos = 0
m = self._recomp.search(s, pos)
while m is not None:
start = m.start()
if pos!= start:
stream.write(s[pos:start])
pos = m.end()
cs = m.group(1) # Color code string
t = m.group(2) # string for colorize
if not skip:
ct = cs[0] # text color
cb = len(cs)>1 and cs[1] or "_" # BG color
#print "[ct="+str(ct)+"]\n"
#print "[cb="+str(cb)+"]\n"
self._ctxt, self._txtbright = self._tag_table[ct]
self._cbg, self._bgbright = self._tag_table[cb]
v = self._decode_color()
#print "[v="+str(v)+"]\n"
self._setColor(v)
stream.write(t)
v = self._calcResetColor()
self._setColor(v)
else:
stream.write(t)
m = self._recomp.search(s, pos)
#while end
if pos<=len(s):
stream.write(s[pos:])
def writeln(self, s="", skip=False, stream=None):
"""
As `write` but add new line at end.
"""
self.write(s+"\n", skip, stream)
@classmethod
def skipTags(cls, s):
"""
Remove colorize tags from string.
``s`` must be unicode.
Return string.
"""
stream = io.StringIO()
_recomp = re.compile(cls.C_RE_PATT)
s = unicode(s)
pos = 0
m = _recomp.search(s, pos)
while m is not None:
start = m.start()
if pos!= start:
stream.write(s[pos:start])
pos = m.end()
t = m.group(2) # string for colorize
stream.write(t)
m = _recomp.search(s, pos)
#while end
if pos<=len(s):
stream.write(s[pos:])
o = stream.getvalue()
stream.close()
return o
class TermColorsWindows(TermColors):
"""
Windows color scheme.
"""
def __init__(self, stream=None):
super(TermColorsWindows, self).__init__(stream)
self._color_table[self.C_BLACK]=0;
self._color_table[self.C_BLUE]=1;
self._color_table[self.C_GREEN]=2;
self._color_table[self.C_CYAN]=3;
self._color_table[self.C_RED]=4;
self._color_table[self.C_MAGENTA]=5;
self._color_table[self.C_YELLOW]=6;
self._color_table[self.C_WHITE]=7;
#self._default = win32.GetConsoleScreenBufferInfo(win32.STDOUT).wAttributes
#self._reset_value = ctypes.windll.Kernel32.GetConsoleScreenBufferInfo(STDOUT).wAttributes
def _calcColor(self, text_color, text_bright, bg_color, bg_bright):
c = (type(text_color) is int and self._color_table[abs(text_color%8)] or 0)
c = c + (text_bright and 8 or 0)
c = c + (type(bg_color) is int and self._color_table[abs(bg_color%8)] or 0)*16
c = c + (bg_bright and 128 or 0)
return c
def _setColor(self, value):
"""
"""
ctypes.windll.Kernel32.GetStdHandle.restype = ctypes.c_ulong
h = ctypes.windll.Kernel32.GetStdHandle(ctypes.c_ulong(0xfffffff5))
ctypes.windll.Kernel32.SetConsoleTextAttribute(h, value)
def _calcResetColor(self):
"""
Calculate value for reset colors to default.
In windows set dark white on black bacground.
"""
# x-XXX Reset defaultt colors. In Win this is hard-white on black.
return self._calcColor(self.C_WHITE, 0, self.C_BLACK, 0)
class TermColorsUnix(TermColors):
"""
Unix color scheme.
"""
def __init__(self, stream=None):
stream = stream and stream or sys.stdout
super(TermColorsUnix, self).__init__(stream)
self._color_table[self.C_BLACK]=0;
self._color_table[self.C_BLUE]=4;
self._color_table[self.C_GREEN]=2;
self._color_table[self.C_CYAN]=6;
self._color_table[self.C_RED]=1;
self._color_table[self.C_MAGENTA]=5;
self._color_table[self.C_YELLOW]=3;
self._color_table[self.C_WHITE]=7;
def _calcColor(self, text_color, text_bright, bg_color, bg_bright):
ct = 30 + (type(text_color) is int and self._color_table[abs(text_color%8)] or 0)
ct = ct + (text_bright and 60 or 0)
ct = text_color is None and "" or str(ct)
cb = 40 + (type(bg_color) is int and self._color_table[abs(bg_color%8)] or 0)
cb = cb + (bg_bright and 60 or 0)
cb = bg_color is None and "" or str(cb)
c = ct + (len(ct) and ";" or "") + cb
c = len(c) and "\033[" + c + "m"
return c
def _setColor(self, value):
"""
"""
self._stream.write(value)
def _calcResetColor(self):
"""
Calculate value for reset colors to default.
"""
return "\033[0m"
class StreamProxy(object):
"""
Wraps a object (such as stdout), acting as a transparent proxy for all
attribute access.
"""
def wrap_stdout(self):
"""
Wrap sys.stdout to this class.
Register atexit() for restore sys.stdout.
Assign stdout to stream.
If already wrapped, then raise RuntimeError.
"""
if self._stdout_orig:
raise RuntimeError("stdout already wrapped by this instance.")
self._stdout_orig = sys.stdout
self._stream = sys.stdout
sys.stdout = self
atexit.register(self.restore_stdout)
def restore_stdout(self):
"""
Restore sys.stdout.
Also set stream to None.
"""
if self._stdout_orig:
sys.stdout = self._stdout_orig
self._stream = None
self._stdout_orig = None
def __init__(self, stream=None, skip=False, term_colors=None):
"""
:Parameters:
stream
stream for wrapping.
If ``stream`` is None then use sys.stdout.
skip
Skip tags (and do not colorize) if ``True``.
term_colors
TermColors` instance.
"""
self._stdout_orig = None
"""Original sys.stdout"""
stream = stream is None and sys.stdout or stream
if not hasattr(stream, "__enter__") or not hasattr(stream, "__enter__"):
#raise TypeError("wrapped must have context methods (__enter__, __exit__).")
pass
self._skip = skip
self._stream = stream
self.term_colors = (term_colors is not None) and term_colors or TermColors.getDefault()
def __getattr__(self, name):
return getattr(self._stream, name)
def __enter__(self, *args, **kwargs):
# special method lookup bypasses __getattr__/__getattribute__, see
# https://stackoverflow.com/questions/12632894/why-doesnt-getattr-work-with-exit
# thus, contextlib magic methods are not proxied via __getattr__
return self._stream.__enter__(*args, **kwargs)
def __exit__(self, *args, **kwargs):
return self._stream.__exit__(*args, **kwargs)
@property
def closed(self):
stream = self._stream
try:
return stream.closed
except AttributeError:
return True
def write(self, s):
self.term_colors.write(s, self._skip, self._stream)
def setSkipTags(self, skip=False):
"""
Set skip tag. Skip tags at out and do not colorize.
"""
self._skip = skip
def _debug():
if _OSWIN:
print "Windows"
w = TermColorsWindows()
w.reset()
w.red().set()
print "dark red on black"
w.bg().bright().set()
print "dark red on dark white"
w.reset()
print "reset colors"
w.writeln("test TAGS Gw test", False)
w.writeln("test <_>TAGS _ test", False)
w.writeln("test TAGS rW test", False)
w.writeln("test TAGS skip rW test", True)
print "end"
print
print "Unix"
w = TermColorsUnix()
w.reset()
w.red().set()
if _OSWIN: print
print "dark red on black"
w.bg().bright().set()
if _OSWIN: print
print "dark red on dark white"
w.reset()
if _OSWIN: print
print "reset colors"
w.writeln("test Gw TAGS Gw test", False)
w.writeln("test _ <_>TAGS _ test", False)
w.writeln("test rW TAGS rW test", False)
w.writeln("test rW TAGS skip rW test", True)
print "end"
print
print "WRAP STDOUT"
w = StreamProxy()
w.wrap_stdout()
w.term_colors.reset()
print " SYS.STDOUT IS WRAPPED"
w.setSkipTags(True)
print " SKIP TAGS"
w.restore_stdout()
print " STDOUT IS RESTORED"
s = TermColors.skipTags(u" TRY CLASSMETHOD TAGS")
print s
assert s==" TRY CLASSMETHOD TAGS"
# _debug end
if __name__== "__main__":
_debug()