Source code for tea.console.color

__author__ = 'Viktor Kerkez <alefnula@gmail.com>'
__date__ = '20 October 2010'
__copyright__ = 'Copyright (c) 2010 Viktor Kerkez'

import re
import sys
import ctypes

from tea.system import platform


class Color(object):
    black = 'black'
    blue = 'blue'
    green = 'green'
    cyan = 'cyan'
    red = 'red'
    purple = 'purple'
    yellow = 'yellow'
    gray = 'gray'
    white = 'white'
    normal = 'normal'

    @classmethod
    def colors(cls):
        return (cls.black, cls.blue, cls.green, cls.cyan, cls.red, cls.purple,
                cls.yellow, cls.gray, cls.white, cls.normal)

    @classmethod
    def color_re(cls):
        return re.compile(r'\[(?P<dark>dark )?(?P<color>%s)\]' %
                          '|'.join(cls.colors()), re.MULTILINE)


if platform.is_a(platform.WINDOWS | platform.DOTNET):

    # COLORS[fg or bg][is dark][color]
    COLORS = {
        'fg': {
            True: {Color.black: 0x00, Color.blue: 0x01, Color.green: 0x02,
                   Color.cyan: 0x03, Color.red: 0x04, Color.purple: 0x05,
                   Color.yellow: 0x06, Color.white: 0x07, Color.gray: 0x08,
                   Color.normal: 0x07},
            False: {Color.black: 0x00, Color.blue: 0x09, Color.green: 0x0A,
                    Color.cyan: 0x0B, Color.red: 0x0C, Color.purple: 0x0D,
                    Color.yellow: 0x0E, Color.white: 0x0F,
                    Color.gray: 0x07, Color.normal: 0x07}
        },
        'bg': {
            True: {Color.black: 0x00, Color.blue: 0x10, Color.green: 0x20,
                   Color.cyan: 0x30, Color.red: 0x40, Color.purple: 0x50,
                   Color.yellow: 0x60, Color.white: 0x70, Color.gray: 0x80,
                   Color.normal: 0x00},
            False: {Color.black: 0x00, Color.blue: 0x90, Color.green: 0xA0,
                    Color.cyan: 0xB0, Color.red: 0xC0, Color.purple: 0xD0,
                    Color.yellow: 0xE0, Color.white: 0xF0,
                    Color.gray: 0x70, Color.normal: 0x00}
        }
    }

    def _set_color(fg, bg, fg_dark, bg_dark, underlined):
        # System is Windows
        # STD_INPUT_HANDLE = -10
        STD_OUTPUT_HANDLE = -11
        # STD_ERROR_HANDLE = -12

        std_out_handle = ctypes.windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)
        code = 0 | COLORS['fg'][fg_dark][fg] | COLORS['bg'][bg_dark][bg]
        ctypes.windll.kernel32.SetConsoleTextAttribute(std_out_handle, code)


elif platform.is_a(platform.POSIX):

    COLORS = {
        'fg': {
            Color.black: 30, Color.red: 31, Color.green: 32,
            Color.yellow: 33, Color.blue: 34, Color.purple: 35,
            Color.cyan: 36, Color.gray: 37, Color.white: 37,
            Color.normal: 00,
        },
        'bg': {
            Color.black: 40, Color.red: 41, Color.green: 42,
            Color.yellow: 43, Color.blue: 44, Color.purple: 45,
            Color.cyan: 46, Color.gray: 47, Color.white: 47,
            Color.normal: 00,
        }
    }

    def _set_color(fg, bg, fg_dark, bg_dark, underlined):
        args = set()
        if fg != Color.normal:
            if not fg_dark:
                args.add(1)
            args.add(COLORS['fg'][fg])
        if bg != Color.normal:
            args.add(COLORS['bg'][bg])
        if underlined:
            args.add(4)
        # White and gray are special cases
        if fg == Color.white:
            args.add(1)
        if fg == Color.gray:
            if fg_dark:
                args.update([1, 30])
                args.remove(37)
            else:
                args.remove(1)
        sys.stdout.write('\33[%sm' % ';'.join(map(str, args)))

else:
    raise platform.not_supported('tea.console.color')


[docs]def set_color(fg=Color.normal, bg=Color.normal, fg_dark=False, bg_dark=False, underlined=False): """Set the console color. >>> set_color(Color.red, Color.blue) >>> set_color('red', 'blue') >>> set_color() # returns back to normal """ _set_color(fg, bg, fg_dark, bg_dark, underlined)
[docs]def strip_colors(text): """Helper function used to strip out the color tags so other function can determine the real text line lengths. :param str text: Text to strip color tags from :rtype: :obj:`str` :return: Stripped text. """ return Color.color_re().sub('', text)
[docs]def cprint(text, fg=Color.normal, bg=Color.normal, fg_dark=False, bg_dark=False, underlined=False, parse=False): """Prints string in to stdout using colored font. See L{set_color} for more details about colors. :type color: str or list[str] :param color: If color is a string than this is a color in which the text will appear. If color is a list of strings than all desired colors will be used as a mask (this is used when you wan to set foreground and background color). :param str text: Text that needs to be printed. """ if parse: color_re = Color.color_re() lines = text.splitlines() count = len(lines) for i, line in enumerate(lines): previous = 0 end = len(line) for match in color_re.finditer(line): sys.stdout.write(line[previous:match.start()]) d = match.groupdict() set_color(d['color'], fg_dark=False if d['dark'] is None else True) previous = match.end() sys.stdout.write(line[previous:end] + ('\n' if (i < (count - 1) or text[-1] == '\n') else '')) else: set_color(fg, bg, fg_dark, bg_dark, underlined) sys.stdout.write(text) set_color()
[docs]def colorize_output(output, colors, indent=0): """Prints output to console using provided color mappings. Color mapping is dict with regular expressions as key and tuple of two as values. Key is used to match if line should be colorized and tuple contains color to be used and boolean value that indicates if dark foreground is used. For example: >>> CLS = { >>> re.compile(r'^(--- .*)$'): (Color.red, False) >>> } will colorize lines that start with '---' to red. If different parts of line needs to be in different color then dict must be supplied in colors with keys that are named group from regular expression and values that are tuples of color and boolean that indicates if dark foreground is used. For example: >>> CLS = { >>> re.compile(r'^(?P<key>user:\s+)(?P<user>.*)$'): { >>> 'key': (Color.yellow, True), >>> 'user': (Color.cyan, False) >>> } >>> } will colorize line 'user: Some user' so that 'user:' part is yellow with dark foreground and 'Some user' part is cyan without dark foreground. """ for line in output.split('\n'): cprint(' ' * indent) if line == '': cprint('\n') continue for regexp, color_def in colors.items(): if regexp.match(line) is not None: _colorize_single_line(line, regexp, color_def) break else: cprint('%s\n' % line)
def _colorize_single_line(line, regexp, color_def): """Prints single line to console with ability to colorize only parts of line. """ match = regexp.match(line) groupdict = match.groupdict() groups = match.groups() if not groupdict: # no named groups, just colorize whole line color = color_def[0] dark = color_def[1] cprint('%s\n' % line, color, fg_dark=dark) else: rev_groups = {v: k for k, v in groupdict.items()} for part in groups: if part in rev_groups and rev_groups[part] in color_def: group_name = rev_groups[part] cprint(part, color_def[group_name][0], fg_dark=color_def[group_name][1]) else: cprint(part) cprint('\n')