#!/usr/bin/env python
# -*- coding: utf-8 -*-
#-----------------------------------------------------------------------------
# Copyright (C) 2008-2017 Fabrice HARROUET (ENIB)
#
# This file is part of TransProg.
#   http://www.enib.fr/~harrouet/transprog.html
# 
# TransProg is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# TransProg is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
# 
# You should have received a copy of the GNU Lesser General Public License
# along with TransProg.  If not, see <http://www.gnu.org/licenses/>.
#-----------------------------------------------------------------------------

'''

This Python script is invoked as a compiler and colorises the
messages generated by your system compiler.
The main purpose is inspired from the no longer maintained
colorgcc Perl script but this Python script goes a bit further.

Supported compilers are
  gcc, g++, clang, clang++, emcc, em++, icc, icpc, cl.exe and icl.exe

For an explicit usage, simply do:
  color_cc.py mycompiler options ...

For an implicit usage under UNIX:
  ln -s color_cc.py mycompiler
You must ensure this symlink is found in your PATH _before_ the
true compiler executable, so that invoking
  mycompiler options ...
executes this Python script.

There is no implicit usage under Window$ since there are no symlinks :-(
A workaround consists in renaming this script as mycompiler.py.
Then invoking
  mycompiler.py options ...
will look for mycompiler.exe and colorise its messages.

'''

import sys
import os
import subprocess
import re
import platform

def bytes_to_str(b):
  if type(b)!=str: return b.decode(sys.stdout.encoding)
  return b

resetColor      ='\x1b[0m'
blackColor      ='\x1b[0;30m'
boldBlackColor  ='\x1b[1;30m'
redColor        ='\x1b[0;31m'
boldRedColor    ='\x1b[1;31m'
greenColor      ='\x1b[0;32m'
boldGreenColor  ='\x1b[1;32m'
yellowColor     ='\x1b[0;33m'
boldYellowColor ='\x1b[1;33m'
blueColor       ='\x1b[0;34m'
boldBlueColor   ='\x1b[1;34m'
magentaColor    ='\x1b[0;35m'
boldMagentaColor='\x1b[1;35m'
cyanColor       ='\x1b[0;36m'
boldCyanColor   ='\x1b[1;36m'
whiteColor      ='\x1b[0;37m'
boldWhiteColor  ='\x1b[1;37m'

quoteColor=greenColor
errorColor=boldRedColor
warningColor=yellowColor
infoColor=boldBlueColor

compiler=None
thisScript=os.path.basename(os.path.realpath(sys.argv[0]))
cmd=os.path.basename(sys.argv[0])
if cmd==thisScript: # explicit usage
  sys.argv=sys.argv[1:]
  cmd=sys.argv[0]
if platform.system()=='Windows':
  sp=os.path.splitext(cmd)
  if sp[1]=='.py' or sp[1]=='':
    cmd=sp[0]+'.exe'
if cmd.find(os.path.sep)!=-1: # path to compiler given
  if os.access(cmd,os.X_OK) and \
     os.path.basename(os.path.realpath(cmd))!=thisScript:
    compiler=cmd
else:
  for i in [i or os.path.curdir
            for i in os.environ['PATH'].split(os.path.pathsep)]:
    i=os.path.join(i,cmd)
    if os.path.isfile(i) and os.access(i,os.X_OK) and \
       os.path.basename(os.path.realpath(i))!=thisScript:
      compiler=i
      break
if not compiler:
  sys.stderr.write('Cannot find any `%s\' compiler different from `%s\'\n'
                   %(cmd,thisScript))
  sys.exit(1)
cmdLine=[compiler]+sys.argv[1:]
# sys.stdout.write('%s\n'%(cmdLine))

def gccLike(cmd):
  for name in ['gcc','g++','clang','emcc','em++']:
    if cmd.find(name)!=-1: return True
  return False

def iccLike(cmd):
  for name in ['icc','icpc','icl.exe']:
    if cmd.find(name)!=-1: return True
  return False

def clLike(cmd):
  for name in ['cl.exe']:
    if cmd.find(name)!=-1: return True
  return False

msgOutput=sys.stderr
if platform.system()=='Windows' and not gccLike(cmd):
  msgOutput=sys.stdout

if not os.isatty(msgOutput.fileno()):
  os.execv(cmdLine[0],cmdLine)
  sys.exit(127)

if platform.system()=='Windows':
  import ctypes
  import struct
  handle=ctypes.windll.kernel32.GetStdHandle(-11) # stdout
  attribs=ctypes.create_string_buffer(22)
  ctypes.windll.kernel32.GetConsoleScreenBufferInfo(handle,attribs)
  (szx,szy,posx,posy,consAttr,winl,wint,winr,winb,maxszx,maxszy)= \
  struct.unpack('hhhhHhhhhhh',attribs.raw)
  colors=[(resetColor  ,consAttr),
          (blackColor  ,0x00)    ,(boldBlackColor  ,0x08),
          (redColor    ,0x04)    ,(boldRedColor    ,0x0C),
          (greenColor  ,0x02)    ,(boldGreenColor  ,0x0A),
          (yellowColor ,0x06)    ,(boldYellowColor ,0x0E),
          (blueColor   ,0x01)    ,(boldBlueColor   ,0x09),
          (magentaColor,0x05)    ,(boldMagentaColor,0x0D),
          (cyanColor   ,0x03)    ,(boldCyanColor   ,0x0B),
          (whiteColor  ,0x07)    ,(boldWhiteColor  ,0x0F)]
  def consoleWrite(l):
    while True:
      pos=l.find('\x1b[')
      if pos==-1:
        msgOutput.write(l)
        msgOutput.flush()
        break
      msgOutput.write(l[0:pos])
      msgOutput.flush()
      l=l[pos:]
      for (c,a) in colors:
        if l.startswith(c):
          ctypes.windll.kernel32.SetConsoleTextAttribute(handle,a)
          l=l[len(c):]
          break
else:
  def consoleWrite(l):
    msgOutput.write(l)

if gccLike(cmd):
  errorRe=re.compile(r'^([^:]*:)([0-9]+(:[0-9]+)?)([:,].*)$')
  infoRe=re.compile(r'^([^:]*)(:.*:)$')
  def colorise(l):
    global currentColor
    m=errorRe.match(l)
    if m:
      if len(m.group(4))==1 or m.group(4).find('note')!=-1:
        currentColor=infoColor
      elif m.group(4).find('err')!=-1:
        currentColor=errorColor
      else:
        currentColor=warningColor
      l=m.group(1)
      l+=quoteColor
      l+=m.group(2)
      l+=currentColor
      l+=m.group(4)
    else:
      m=infoRe.match(l)
      if m:
        currentColor=infoColor
    return l
elif iccLike(cmd):
  errorRe1=re.compile(r'^(.*\()([0-9]+)(\):([^:]+):.*)$')
  errorRe2=re.compile(r'^(.*)(:([^:]+):.*)$')
  def colorise(l):
    global currentColor
    m=errorRe1.match(l)
    if m:
      if m.group(4).find('err')!=-1:
        currentColor=errorColor
      else:
        currentColor=warningColor
      l=m.group(1)
      l+=quoteColor
      l+=m.group(2)
      l+=currentColor
      l+=m.group(3)
    else:
      m=errorRe2.match(l)
      if m:
        if m.group(3).find('err')!=-1:
          currentColor=errorColor
        else:
          currentColor=warningColor
    return l
elif clLike(cmd):
  errorRe1=re.compile(r'^(.*\()([0-9]+)(\) :([^:]+):.*)$')
  errorRe2=re.compile(r'^(.*)( :([^:]+):.*)$')
  def colorise(l):
    global currentColor
    m=errorRe1.match(l)
    if m:
      if m.group(4).find('err')!=-1:
        currentColor=errorColor
      else:
        currentColor=warningColor
      l=m.group(1)
      l+=quoteColor
      l+=m.group(2)
      l+=currentColor
      l+=m.group(3)
    else:
      m=errorRe2.match(l)
      if m:
        if m.group(3).find('err')!=-1:
          currentColor=errorColor
        else:
          currentColor=warningColor
    return l
else:
  def colorise(l):
    global currentColor
    return l

if msgOutput is sys.stdout:
  proc=subprocess.Popen(cmdLine,stdout=subprocess.PIPE)
  msgInput=proc.stdout
else:
  proc=subprocess.Popen(cmdLine,stderr=subprocess.PIPE)
  msgInput=proc.stderr
quoteRe=re.compile(r'''(«[^«»]+»|`[^`']+'|'[^']+'|‘[^’]+’|"[^"]+")''')
currentColor=resetColor+infoColor
for l in msgInput:
  l=bytes_to_str(l)
  if l.endswith('\n'):
    l=l[:-1]
  if l:
    # sys.stdout.write('%s\n'%(l))
    l=colorise(l)
    l=re.sub(quoteRe,r'%s\1%s'%(quoteColor,currentColor),l)
  else:
    currentColor=resetColor+infoColor
  consoleWrite(currentColor+l+resetColor+'\n')
proc.wait()
sys.exit(proc.returncode)

#-----------------------------------------------------------------------------
