Colorization for MSVC warnings and errors
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import sys, re | |
COMPILE_PATTERN = re.compile(r'^.+\(\d+\) ?: (fatal )?(error|warning|note)( C\d+)?: .+$') | |
LINK_PATTERN = re.compile(r'^.+ : (fatal )?(error|warning) LNK\d+: .+$') | |
OTHER_PATTERN = re.compile(r'^.+ : (general |Command line )?(error|warning) \w+ ?: .+$') | |
class WinColorStream: | |
# 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. | |
FOREGROUND_WHITE = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | |
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. | |
DEFAULT = FOREGROUND_WHITE | |
ERROR = FOREGROUND_RED | FOREGROUND_INTENSITY | |
WARNING = FOREGROUND_YELLOW | FOREGROUND_INTENSITY | |
INFO = FOREGROUND_CYAN | |
def __init__(self, stream): | |
import ctypes, ctypes.util, ctypes.wintypes | |
self._stream = stream | |
# for some reason find_msvcrt() sometimes doesn't find msvcrt.dll on my system? | |
crtname = ctypes.util.find_msvcrt() | |
if not crtname: | |
crtname = ctypes.util.find_library('msvcrt') | |
crtlib = ctypes.cdll.LoadLibrary(crtname) | |
# get file handle for the stream | |
self._outhdl = crtlib._get_osfhandle(self._stream.fileno()) | |
# get color setter function | |
self._set_console_text_attribute = ctypes.windll.kernel32.SetConsoleTextAttribute | |
self._set_console_text_attribute.argtypes = (ctypes.wintypes.HANDLE, ctypes.wintypes.WORD) | |
def _set_color(self, code): | |
self._set_console_text_attribute(self._outhdl, code) | |
def write(self, text, color): | |
self._set_color(color) | |
self._stream.write(text) | |
self._stream.flush() | |
self._set_color(self.DEFAULT) | |
def find_category(line): | |
match = COMPILE_PATTERN.match(line) | |
if not match: | |
match = LINK_PATTERN.match(line) | |
if not match: | |
match = OTHER_PATTERN.match(line) | |
if match: | |
return match.group(2) | |
def parse_int_arg(s): | |
"""Parse int to text with any base, i.e. supporting '0x' or '0' prefixes.""" | |
return int(s, 0) | |
def parse_args(args): | |
import argparse | |
parser = argparse.ArgumentParser() | |
parser.add_argument('--summary', action='store_true', | |
help='collect all errors and warnings and show a summary of them after compilation.') | |
parser.add_argument('--filter', action='store_true', | |
help='show only colored messages, i.e. errors, warnings and notes.') | |
parser.add_argument('--error', type=parse_int_arg, default=WinColorStream.ERROR, | |
help='set a custom color for error lines, see Win32 API SetConsoleTextAttribute() for possible values.') | |
parser.add_argument('--warning', type=parse_int_arg, default=WinColorStream.WARNING, | |
help='set a custom color for warning lines, see Win32 API SetConsoleTextAttribute() for possible values.') | |
parser.add_argument('--note', type=parse_int_arg, default=WinColorStream.INFO, | |
help='set a custom color for note lines, see Win32 API SetConsoleTextAttribute() for possible values.') | |
return parser.parse_args(args) | |
def main(args): | |
options = parse_args(args) | |
colors = { 'error': options.error, 'warning': options.warning, 'note': options.note } | |
stream = WinColorStream(sys.stdout) | |
warnings = [] | |
errors = [] | |
try: | |
while True: | |
line = sys.stdin.readline() | |
if not line: | |
break | |
category = find_category(line) | |
if options.filter and category is None: | |
continue | |
if options.summary: | |
if category == 'error': | |
errors.append(line) | |
elif category == 'warning': | |
warnings.append(line) | |
color = colors.get(category, WinColorStream.DEFAULT) | |
stream.write(line, color) | |
except KeyboardInterrupt: | |
pass # ctrl+c, stop | |
if errors or warnings: | |
stream.write('\nSummary:\n\n', WinColorStream.DEFAULT) | |
for line in warnings: | |
stream.write(line, options.warning) | |
for line in errors: | |
stream.write(line, options.error) | |
summary_color = options.error if errors else options.warning | |
stream.write('\n{} Warning{}, {} Error{}\n\n'.format( | |
len(warnings), '' if len(warnings) == 1 else 's', | |
len(errors), '' if len(errors) == 1 else 's'), | |
summary_color) | |
if __name__ == '__main__': | |
main(sys.argv[1:]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment