1. Yuki KODAMA
  2. win32color

Source

win32color / win32color.py

# win32color.py - 'Color Extension' for Windows command prompt
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2, incorporated herein by reference.
#
# originally: color.py
# http://www.selenic.com/mercurial/wiki/ColorExtension

"""
color_console.py:
http://www.burgaud.com/bring-colors-to-the-windows-console-with-python/

Colors text in console mode application (win32).
Uses ctypes and Win32 methods SetConsoleTextAttribute and
GetConsoleScreenBufferInfo.

$Id: color_console.py 534 2009-05-10 04:00:59Z andre $

"""

from ctypes import windll, Structure, c_short, c_ushort, byref

SHORT = c_short
WORD = c_ushort

class COORD(Structure):
  """struct in wincon.h."""
  _fields_ = [
    ("X", SHORT),
    ("Y", SHORT)]

class SMALL_RECT(Structure):
  """struct in wincon.h."""
  _fields_ = [
    ("Left", SHORT),
    ("Top", SHORT),
    ("Right", SHORT),
    ("Bottom", SHORT)]

class CONSOLE_SCREEN_BUFFER_INFO(Structure):
  """struct in wincon.h."""
  _fields_ = [
    ("dwSize", COORD),
    ("dwCursorPosition", COORD),
    ("wAttributes", WORD),
    ("srWindow", SMALL_RECT),
    ("dwMaximumWindowSize", COORD)]

# winbase.h
STD_INPUT_HANDLE = -10
STD_OUTPUT_HANDLE = -11
STD_ERROR_HANDLE = -12

# wincon.h
FOREGROUND_BLACK     = 0x0000
FOREGROUND_BLUE      = 0x0001
FOREGROUND_GREEN     = 0x0002
FOREGROUND_CYAN      = 0x0003
FOREGROUND_RED       = 0x0004
FOREGROUND_MAGENTA   = 0x0005
FOREGROUND_YELLOW    = 0x0006
FOREGROUND_GREY      = 0x0007
FOREGROUND_INTENSITY = 0x0008 # foreground color is intensified.

BACKGROUND_BLACK     = 0x0000
BACKGROUND_BLUE      = 0x0010
BACKGROUND_GREEN     = 0x0020
BACKGROUND_CYAN      = 0x0030
BACKGROUND_RED       = 0x0040
BACKGROUND_MAGENTA   = 0x0050
BACKGROUND_YELLOW    = 0x0060
BACKGROUND_GREY      = 0x0070
BACKGROUND_INTENSITY = 0x0080 # background color is intensified.

stdout_handle = windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)
SetConsoleTextAttribute = windll.kernel32.SetConsoleTextAttribute
GetConsoleScreenBufferInfo = windll.kernel32.GetConsoleScreenBufferInfo

def get_text_attr():
  """Returns the character attributes (colors) of the console screen
  buffer."""
  csbi = CONSOLE_SCREEN_BUFFER_INFO()
  GetConsoleScreenBufferInfo(stdout_handle, byref(csbi))
  return csbi.wAttributes

def set_text_attr(color):
  """Sets the character attributes (colors) of the console screen
  buffer. Color is a combination of foreground and background color,
  foreground and background intensity."""
  SetConsoleTextAttribute(stdout_handle, color)


import os, re, sys

from mercurial import cmdutil, commands, extensions
from mercurial.i18n import _

"""
See also:
http://msdn.microsoft.com/en-us/library/ms682088(VS.85).aspx#_win32_character_attributes
"""

# start and stop parameters for effects
_attributes = {'none': 0,
               'black': FOREGROUND_BLACK,
               'red': FOREGROUND_RED | FOREGROUND_INTENSITY,
               'green': FOREGROUND_GREEN | FOREGROUND_INTENSITY,
               'yellow': FOREGROUND_YELLOW | FOREGROUND_INTENSITY,
               'blue': FOREGROUND_BLUE | FOREGROUND_INTENSITY,
               'magenta': FOREGROUND_MAGENTA | FOREGROUND_INTENSITY,
               'cyan': FOREGROUND_CYAN | FOREGROUND_INTENSITY,
               'white': FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE,
               'grey': FOREGROUND_BLACK | FOREGROUND_INTENSITY,
               'bold': 0,
               'italic': 0,
               'underline': 0,
               'inverse': BACKGROUND_INTENSITY,
               'black_background': BACKGROUND_BLACK,
               'red_background': BACKGROUND_RED | BACKGROUND_INTENSITY,
               'green_background': BACKGROUND_GREEN | BACKGROUND_INTENSITY,
               'yellow_background': BACKGROUND_YELLOW | BACKGROUND_INTENSITY,
               'blue_background': BACKGROUND_BLUE | BACKGROUND_INTENSITY,
               'purple_background': BACKGROUND_MAGENTA | BACKGROUND_INTENSITY,
               'cyan_background': BACKGROUND_CYAN | BACKGROUND_INTENSITY,
               'white_background': BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE,
               'grey_background': BACKGROUND_BLACK | BACKGROUND_INTENSITY}

def set_render_attr(*effects):
    default_attr = get_text_attr()
    new_attr = 0
    for effect in effects:
        attr = _attributes[effect]
        if attr != 0:
            new_attr |= attr
    if new_attr != 0:
        set_text_attr(new_attr)
        return (lambda: set_text_attr(default_attr))
    else:
        return (lambda: None)

def colorstatus(orig, ui, repo, *pats, **opts):
    '''run the status command with colored output'''

    delimiter = opts['print0'] and '\0' or '\n'

    nostatus = opts.get('no_status')
    opts['no_status'] = False
    # run status and capture its output
    ui.pushbuffer()
    retval = orig(ui, repo, *pats, **opts)
    # filter out empty strings
    lines_with_status = [ line for line in ui.popbuffer().split(delimiter) if line ]

    if nostatus:
        lines = [l[2:] for l in lines_with_status]
    else:
        lines = lines_with_status

    # apply color to output and display it
    for i in xrange(0, len(lines)):
        status = _status_abbreviations[lines_with_status[i][0]]
        effects = _status_effects[status]
        if effects:
            restore = set_render_attr(*effects)
            ui.write(lines[i] + delimiter)
            restore()
        else:
            ui.write(lines[i] + delimiter)
    return retval

_status_abbreviations = { 'M': 'modified',
                          'A': 'added',
                          'R': 'removed',
                          '!': 'deleted',
                          '?': 'unknown',
                          'I': 'ignored',
                          'C': 'clean',
                          ' ': 'copied', }

_status_effects = { 'modified': ('blue', 'bold'),
                    'added': ('green', 'bold'),
                    'removed': ('red', 'bold'),
                    'deleted': ('cyan', 'bold', 'underline'),
                    'unknown': ('magenta', 'bold', 'underline'),
                    'ignored': ('black', 'bold'),
                    'clean': ('none', ),
                    'copied': ('none', ), }

def render_effects(text, *effects):
    return text

def colorqseries(orig, ui, repo, *dummy, **opts):
    '''run the qseries command with colored output'''
    ui.pushbuffer()
    retval = orig(ui, repo, **opts)
    patches = ui.popbuffer().splitlines()
    for patch in patches:
        patchname = patch
        if opts['summary']:
            patchname = patchname.split(': ')[0]
        if ui.verbose:
            patchname = patchname.split(' ', 2)[-1]

        if opts['missing']:
            effects = _patch_effects['missing']
        # Determine if patch is applied.
        elif [ applied for applied in repo.mq.applied
               if patchname == applied.name ]:
            effects = _patch_effects['applied']
        else:
            effects = _patch_effects['unapplied']
        restore = set_render_attr(*effects)
        ui.write(patch + '\n')
        restore()
    return retval

_patch_effects = { 'applied': ('blue', 'bold', 'underline'),
                   'missing': ('red', 'bold'),
                   'unapplied': ('black', 'bold'), }

def colorwrap(orig, s):
    '''wrap ui.write for colored diff output'''
    lines = s.split('\n')
    for i, line in enumerate(lines):
        stripline = line
        if line and line[0] in '+-':
            # highlight trailing whitespace, but only in changed lines
            stripline = line.rstrip()
        for prefix, style in _diff_prefixes:
            if stripline.startswith(prefix):
                lines[i] = render_effects(stripline, *_diff_effects[style])
                break
        if line != stripline:
            lines[i] += render_effects(
                line[len(stripline):], *_diff_effects['trailingwhitespace'])
    orig('\n'.join(lines))

def colorshowpatch(orig, self, node):
    '''wrap cmdutil.changeset_printer.showpatch with colored output'''
    oldwrite = extensions.wrapfunction(self.ui, 'write', colorwrap)
    try:
        orig(self, node)
    finally:
        self.ui.write = oldwrite

def colordiff(orig, ui, repo, *pats, **opts):
    '''run the diff command with colored output'''
    oldwrite = extensions.wrapfunction(ui, 'write', colorwrap)
    try:
        orig(ui, repo, *pats, **opts)
    finally:
        ui.write = oldwrite

_diff_prefixes = [('diff', 'diffline'),
                  ('copy', 'extended'),
                  ('rename', 'extended'),
                  ('old', 'extended'),
                  ('new', 'extended'),
                  ('deleted', 'extended'),
                  ('---', 'file_a'),
                  ('+++', 'file_b'),
                  ('@', 'hunk'),
                  ('-', 'deleted'),
                  ('+', 'inserted')]

_diff_effects = {'diffline': ('bold',),
                 'extended': ('cyan', 'bold'),
                 'file_a': ('red', 'bold'),
                 'file_b': ('green', 'bold'),
                 'hunk': ('magenta',),
                 'deleted': ('red',),
                 'inserted': ('green',),
                 'changed': ('white',),
                 'trailingwhitespace': ('bold', 'red_background'),}

def uisetup(ui):
    '''Initialize the extension.'''
    _setupcmd(ui, 'diff', commands.table, colordiff, _diff_effects)
    _setupcmd(ui, 'incoming', commands.table, None, _diff_effects)
    _setupcmd(ui, 'log', commands.table, None, _diff_effects)
    _setupcmd(ui, 'outgoing', commands.table, None, _diff_effects)
    _setupcmd(ui, 'tip', commands.table, None, _diff_effects)
    _setupcmd(ui, 'status', commands.table, colorstatus, _status_effects)
    try:
        mq = extensions.find('mq')
        _setupcmd(ui, 'qdiff', mq.cmdtable, colordiff, _diff_effects)
        _setupcmd(ui, 'qseries', mq.cmdtable, colorqseries, _patch_effects)
    except KeyError:
        # The mq extension is not enabled
        pass

def _setupcmd(ui, cmd, table, func, effectsmap):
    '''patch in command to command table and load effect map'''
    def nocolor(orig, *args, **opts):

        if (opts['no_color'] or opts['color'] == 'never' or
            (opts['color'] == 'auto' and (os.environ.get('TERM') == 'dumb'
                                          or not sys.__stdout__.isatty()))):
            return orig(*args, **opts)

        oldshowpatch = extensions.wrapfunction(cmdutil.changeset_printer,
                                               'showpatch', colorshowpatch)
        try:
            if func is not None:
                return func(orig, *args, **opts)
            return orig(*args, **opts)
        finally:
            cmdutil.changeset_printer.showpatch = oldshowpatch

    entry = extensions.wrapcommand(table, cmd, nocolor)
    entry[1].extend([
        ('', 'color', 'auto', _("when to colorize (always, auto, or never)")),
        ('', 'no-color', None, _("don't colorize output")),
    ])

    for status in effectsmap:
        effects = ui.config('color', cmd + '.' + status)
        if effects:
            effectsmap[status] = re.split('\W+', effects)