429 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			429 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2014 beego Author. All Rights Reserved.
 | |
| //
 | |
| // Licensed under the Apache License, Version 2.0 (the "License");
 | |
| // you may not use this file except in compliance with the License.
 | |
| // You may obtain a copy of the License at
 | |
| //
 | |
| //      http://www.apache.org/licenses/LICENSE-2.0
 | |
| //
 | |
| // Unless required by applicable law or agreed to in writing, software
 | |
| // distributed under the License is distributed on an "AS IS" BASIS,
 | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| // See the License for the specific language governing permissions and
 | |
| // limitations under the License.
 | |
| 
 | |
| // +build windows
 | |
| 
 | |
| package logs
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"io"
 | |
| 	"strings"
 | |
| 	"syscall"
 | |
| 	"unsafe"
 | |
| )
 | |
| 
 | |
| type (
 | |
| 	csiState    int
 | |
| 	parseResult int
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	outsideCsiCode csiState = iota
 | |
| 	firstCsiCode
 | |
| 	secondCsiCode
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	noConsole parseResult = iota
 | |
| 	changedColor
 | |
| 	unknown
 | |
| )
 | |
| 
 | |
| type ansiColorWriter struct {
 | |
| 	w             io.Writer
 | |
| 	mode          outputMode
 | |
| 	state         csiState
 | |
| 	paramStartBuf bytes.Buffer
 | |
| 	paramBuf      bytes.Buffer
 | |
| }
 | |
| 
 | |
| const (
 | |
| 	firstCsiChar   byte = '\x1b'
 | |
| 	secondeCsiChar byte = '['
 | |
| 	separatorChar  byte = ';'
 | |
| 	sgrCode        byte = 'm'
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	foregroundBlue      = uint16(0x0001)
 | |
| 	foregroundGreen     = uint16(0x0002)
 | |
| 	foregroundRed       = uint16(0x0004)
 | |
| 	foregroundIntensity = uint16(0x0008)
 | |
| 	backgroundBlue      = uint16(0x0010)
 | |
| 	backgroundGreen     = uint16(0x0020)
 | |
| 	backgroundRed       = uint16(0x0040)
 | |
| 	backgroundIntensity = uint16(0x0080)
 | |
| 	underscore          = uint16(0x8000)
 | |
| 
 | |
| 	foregroundMask = foregroundBlue | foregroundGreen | foregroundRed | foregroundIntensity
 | |
| 	backgroundMask = backgroundBlue | backgroundGreen | backgroundRed | backgroundIntensity
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	ansiReset        = "0"
 | |
| 	ansiIntensityOn  = "1"
 | |
| 	ansiIntensityOff = "21"
 | |
| 	ansiUnderlineOn  = "4"
 | |
| 	ansiUnderlineOff = "24"
 | |
| 	ansiBlinkOn      = "5"
 | |
| 	ansiBlinkOff     = "25"
 | |
| 
 | |
| 	ansiForegroundBlack   = "30"
 | |
| 	ansiForegroundRed     = "31"
 | |
| 	ansiForegroundGreen   = "32"
 | |
| 	ansiForegroundYellow  = "33"
 | |
| 	ansiForegroundBlue    = "34"
 | |
| 	ansiForegroundMagenta = "35"
 | |
| 	ansiForegroundCyan    = "36"
 | |
| 	ansiForegroundWhite   = "37"
 | |
| 	ansiForegroundDefault = "39"
 | |
| 
 | |
| 	ansiBackgroundBlack   = "40"
 | |
| 	ansiBackgroundRed     = "41"
 | |
| 	ansiBackgroundGreen   = "42"
 | |
| 	ansiBackgroundYellow  = "43"
 | |
| 	ansiBackgroundBlue    = "44"
 | |
| 	ansiBackgroundMagenta = "45"
 | |
| 	ansiBackgroundCyan    = "46"
 | |
| 	ansiBackgroundWhite   = "47"
 | |
| 	ansiBackgroundDefault = "49"
 | |
| 
 | |
| 	ansiLightForegroundGray    = "90"
 | |
| 	ansiLightForegroundRed     = "91"
 | |
| 	ansiLightForegroundGreen   = "92"
 | |
| 	ansiLightForegroundYellow  = "93"
 | |
| 	ansiLightForegroundBlue    = "94"
 | |
| 	ansiLightForegroundMagenta = "95"
 | |
| 	ansiLightForegroundCyan    = "96"
 | |
| 	ansiLightForegroundWhite   = "97"
 | |
| 
 | |
| 	ansiLightBackgroundGray    = "100"
 | |
| 	ansiLightBackgroundRed     = "101"
 | |
| 	ansiLightBackgroundGreen   = "102"
 | |
| 	ansiLightBackgroundYellow  = "103"
 | |
| 	ansiLightBackgroundBlue    = "104"
 | |
| 	ansiLightBackgroundMagenta = "105"
 | |
| 	ansiLightBackgroundCyan    = "106"
 | |
| 	ansiLightBackgroundWhite   = "107"
 | |
| )
 | |
| 
 | |
| type drawType int
 | |
| 
 | |
| const (
 | |
| 	foreground drawType = iota
 | |
| 	background
 | |
| )
 | |
| 
 | |
| type winColor struct {
 | |
| 	code     uint16
 | |
| 	drawType drawType
 | |
| }
 | |
| 
 | |
| var colorMap = map[string]winColor{
 | |
| 	ansiForegroundBlack:   {0, foreground},
 | |
| 	ansiForegroundRed:     {foregroundRed, foreground},
 | |
| 	ansiForegroundGreen:   {foregroundGreen, foreground},
 | |
| 	ansiForegroundYellow:  {foregroundRed | foregroundGreen, foreground},
 | |
| 	ansiForegroundBlue:    {foregroundBlue, foreground},
 | |
| 	ansiForegroundMagenta: {foregroundRed | foregroundBlue, foreground},
 | |
| 	ansiForegroundCyan:    {foregroundGreen | foregroundBlue, foreground},
 | |
| 	ansiForegroundWhite:   {foregroundRed | foregroundGreen | foregroundBlue, foreground},
 | |
| 	ansiForegroundDefault: {foregroundRed | foregroundGreen | foregroundBlue, foreground},
 | |
| 
 | |
| 	ansiBackgroundBlack:   {0, background},
 | |
| 	ansiBackgroundRed:     {backgroundRed, background},
 | |
| 	ansiBackgroundGreen:   {backgroundGreen, background},
 | |
| 	ansiBackgroundYellow:  {backgroundRed | backgroundGreen, background},
 | |
| 	ansiBackgroundBlue:    {backgroundBlue, background},
 | |
| 	ansiBackgroundMagenta: {backgroundRed | backgroundBlue, background},
 | |
| 	ansiBackgroundCyan:    {backgroundGreen | backgroundBlue, background},
 | |
| 	ansiBackgroundWhite:   {backgroundRed | backgroundGreen | backgroundBlue, background},
 | |
| 	ansiBackgroundDefault: {0, background},
 | |
| 
 | |
| 	ansiLightForegroundGray:    {foregroundIntensity, foreground},
 | |
| 	ansiLightForegroundRed:     {foregroundIntensity | foregroundRed, foreground},
 | |
| 	ansiLightForegroundGreen:   {foregroundIntensity | foregroundGreen, foreground},
 | |
| 	ansiLightForegroundYellow:  {foregroundIntensity | foregroundRed | foregroundGreen, foreground},
 | |
| 	ansiLightForegroundBlue:    {foregroundIntensity | foregroundBlue, foreground},
 | |
| 	ansiLightForegroundMagenta: {foregroundIntensity | foregroundRed | foregroundBlue, foreground},
 | |
| 	ansiLightForegroundCyan:    {foregroundIntensity | foregroundGreen | foregroundBlue, foreground},
 | |
| 	ansiLightForegroundWhite:   {foregroundIntensity | foregroundRed | foregroundGreen | foregroundBlue, foreground},
 | |
| 
 | |
| 	ansiLightBackgroundGray:    {backgroundIntensity, background},
 | |
| 	ansiLightBackgroundRed:     {backgroundIntensity | backgroundRed, background},
 | |
| 	ansiLightBackgroundGreen:   {backgroundIntensity | backgroundGreen, background},
 | |
| 	ansiLightBackgroundYellow:  {backgroundIntensity | backgroundRed | backgroundGreen, background},
 | |
| 	ansiLightBackgroundBlue:    {backgroundIntensity | backgroundBlue, background},
 | |
| 	ansiLightBackgroundMagenta: {backgroundIntensity | backgroundRed | backgroundBlue, background},
 | |
| 	ansiLightBackgroundCyan:    {backgroundIntensity | backgroundGreen | backgroundBlue, background},
 | |
| 	ansiLightBackgroundWhite:   {backgroundIntensity | backgroundRed | backgroundGreen | backgroundBlue, background},
 | |
| }
 | |
| 
 | |
| var (
 | |
| 	kernel32                       = syscall.NewLazyDLL("kernel32.dll")
 | |
| 	procSetConsoleTextAttribute    = kernel32.NewProc("SetConsoleTextAttribute")
 | |
| 	procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
 | |
| 	defaultAttr                    *textAttributes
 | |
| )
 | |
| 
 | |
| func init() {
 | |
| 	screenInfo := getConsoleScreenBufferInfo(uintptr(syscall.Stdout))
 | |
| 	if screenInfo != nil {
 | |
| 		colorMap[ansiForegroundDefault] = winColor{
 | |
| 			screenInfo.WAttributes & (foregroundRed | foregroundGreen | foregroundBlue),
 | |
| 			foreground,
 | |
| 		}
 | |
| 		colorMap[ansiBackgroundDefault] = winColor{
 | |
| 			screenInfo.WAttributes & (backgroundRed | backgroundGreen | backgroundBlue),
 | |
| 			background,
 | |
| 		}
 | |
| 		defaultAttr = convertTextAttr(screenInfo.WAttributes)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| type coord struct {
 | |
| 	X, Y int16
 | |
| }
 | |
| 
 | |
| type smallRect struct {
 | |
| 	Left, Top, Right, Bottom int16
 | |
| }
 | |
| 
 | |
| type consoleScreenBufferInfo struct {
 | |
| 	DwSize              coord
 | |
| 	DwCursorPosition    coord
 | |
| 	WAttributes         uint16
 | |
| 	SrWindow            smallRect
 | |
| 	DwMaximumWindowSize coord
 | |
| }
 | |
| 
 | |
| func getConsoleScreenBufferInfo(hConsoleOutput uintptr) *consoleScreenBufferInfo {
 | |
| 	var csbi consoleScreenBufferInfo
 | |
| 	ret, _, _ := procGetConsoleScreenBufferInfo.Call(
 | |
| 		hConsoleOutput,
 | |
| 		uintptr(unsafe.Pointer(&csbi)))
 | |
| 	if ret == 0 {
 | |
| 		return nil
 | |
| 	}
 | |
| 	return &csbi
 | |
| }
 | |
| 
 | |
| func setConsoleTextAttribute(hConsoleOutput uintptr, wAttributes uint16) bool {
 | |
| 	ret, _, _ := procSetConsoleTextAttribute.Call(
 | |
| 		hConsoleOutput,
 | |
| 		uintptr(wAttributes))
 | |
| 	return ret != 0
 | |
| }
 | |
| 
 | |
| type textAttributes struct {
 | |
| 	foregroundColor     uint16
 | |
| 	backgroundColor     uint16
 | |
| 	foregroundIntensity uint16
 | |
| 	backgroundIntensity uint16
 | |
| 	underscore          uint16
 | |
| 	otherAttributes     uint16
 | |
| }
 | |
| 
 | |
| func convertTextAttr(winAttr uint16) *textAttributes {
 | |
| 	fgColor := winAttr & (foregroundRed | foregroundGreen | foregroundBlue)
 | |
| 	bgColor := winAttr & (backgroundRed | backgroundGreen | backgroundBlue)
 | |
| 	fgIntensity := winAttr & foregroundIntensity
 | |
| 	bgIntensity := winAttr & backgroundIntensity
 | |
| 	underline := winAttr & underscore
 | |
| 	otherAttributes := winAttr &^ (foregroundMask | backgroundMask | underscore)
 | |
| 	return &textAttributes{fgColor, bgColor, fgIntensity, bgIntensity, underline, otherAttributes}
 | |
| }
 | |
| 
 | |
| func convertWinAttr(textAttr *textAttributes) uint16 {
 | |
| 	var winAttr uint16
 | |
| 	winAttr |= textAttr.foregroundColor
 | |
| 	winAttr |= textAttr.backgroundColor
 | |
| 	winAttr |= textAttr.foregroundIntensity
 | |
| 	winAttr |= textAttr.backgroundIntensity
 | |
| 	winAttr |= textAttr.underscore
 | |
| 	winAttr |= textAttr.otherAttributes
 | |
| 	return winAttr
 | |
| }
 | |
| 
 | |
| func changeColor(param []byte) parseResult {
 | |
| 	screenInfo := getConsoleScreenBufferInfo(uintptr(syscall.Stdout))
 | |
| 	if screenInfo == nil {
 | |
| 		return noConsole
 | |
| 	}
 | |
| 
 | |
| 	winAttr := convertTextAttr(screenInfo.WAttributes)
 | |
| 	strParam := string(param)
 | |
| 	if len(strParam) <= 0 {
 | |
| 		strParam = "0"
 | |
| 	}
 | |
| 	csiParam := strings.Split(strParam, string(separatorChar))
 | |
| 	for _, p := range csiParam {
 | |
| 		c, ok := colorMap[p]
 | |
| 		switch {
 | |
| 		case !ok:
 | |
| 			switch p {
 | |
| 			case ansiReset:
 | |
| 				winAttr.foregroundColor = defaultAttr.foregroundColor
 | |
| 				winAttr.backgroundColor = defaultAttr.backgroundColor
 | |
| 				winAttr.foregroundIntensity = defaultAttr.foregroundIntensity
 | |
| 				winAttr.backgroundIntensity = defaultAttr.backgroundIntensity
 | |
| 				winAttr.underscore = 0
 | |
| 				winAttr.otherAttributes = 0
 | |
| 			case ansiIntensityOn:
 | |
| 				winAttr.foregroundIntensity = foregroundIntensity
 | |
| 			case ansiIntensityOff:
 | |
| 				winAttr.foregroundIntensity = 0
 | |
| 			case ansiUnderlineOn:
 | |
| 				winAttr.underscore = underscore
 | |
| 			case ansiUnderlineOff:
 | |
| 				winAttr.underscore = 0
 | |
| 			case ansiBlinkOn:
 | |
| 				winAttr.backgroundIntensity = backgroundIntensity
 | |
| 			case ansiBlinkOff:
 | |
| 				winAttr.backgroundIntensity = 0
 | |
| 			default:
 | |
| 				// unknown code
 | |
| 			}
 | |
| 		case c.drawType == foreground:
 | |
| 			winAttr.foregroundColor = c.code
 | |
| 		case c.drawType == background:
 | |
| 			winAttr.backgroundColor = c.code
 | |
| 		}
 | |
| 	}
 | |
| 	winTextAttribute := convertWinAttr(winAttr)
 | |
| 	setConsoleTextAttribute(uintptr(syscall.Stdout), winTextAttribute)
 | |
| 
 | |
| 	return changedColor
 | |
| }
 | |
| 
 | |
| func parseEscapeSequence(command byte, param []byte) parseResult {
 | |
| 	if defaultAttr == nil {
 | |
| 		return noConsole
 | |
| 	}
 | |
| 
 | |
| 	switch command {
 | |
| 	case sgrCode:
 | |
| 		return changeColor(param)
 | |
| 	default:
 | |
| 		return unknown
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (cw *ansiColorWriter) flushBuffer() (int, error) {
 | |
| 	return cw.flushTo(cw.w)
 | |
| }
 | |
| 
 | |
| func (cw *ansiColorWriter) resetBuffer() (int, error) {
 | |
| 	return cw.flushTo(nil)
 | |
| }
 | |
| 
 | |
| func (cw *ansiColorWriter) flushTo(w io.Writer) (int, error) {
 | |
| 	var n1, n2 int
 | |
| 	var err error
 | |
| 
 | |
| 	startBytes := cw.paramStartBuf.Bytes()
 | |
| 	cw.paramStartBuf.Reset()
 | |
| 	if w != nil {
 | |
| 		n1, err = cw.w.Write(startBytes)
 | |
| 		if err != nil {
 | |
| 			return n1, err
 | |
| 		}
 | |
| 	} else {
 | |
| 		n1 = len(startBytes)
 | |
| 	}
 | |
| 	paramBytes := cw.paramBuf.Bytes()
 | |
| 	cw.paramBuf.Reset()
 | |
| 	if w != nil {
 | |
| 		n2, err = cw.w.Write(paramBytes)
 | |
| 		if err != nil {
 | |
| 			return n1 + n2, err
 | |
| 		}
 | |
| 	} else {
 | |
| 		n2 = len(paramBytes)
 | |
| 	}
 | |
| 	return n1 + n2, nil
 | |
| }
 | |
| 
 | |
| func isParameterChar(b byte) bool {
 | |
| 	return ('0' <= b && b <= '9') || b == separatorChar
 | |
| }
 | |
| 
 | |
| func (cw *ansiColorWriter) Write(p []byte) (int, error) {
 | |
| 	var r, nw, first, last int
 | |
| 	if cw.mode != DiscardNonColorEscSeq {
 | |
| 		cw.state = outsideCsiCode
 | |
| 		cw.resetBuffer()
 | |
| 	}
 | |
| 
 | |
| 	var err error
 | |
| 	for i, ch := range p {
 | |
| 		switch cw.state {
 | |
| 		case outsideCsiCode:
 | |
| 			if ch == firstCsiChar {
 | |
| 				cw.paramStartBuf.WriteByte(ch)
 | |
| 				cw.state = firstCsiCode
 | |
| 			}
 | |
| 		case firstCsiCode:
 | |
| 			switch ch {
 | |
| 			case firstCsiChar:
 | |
| 				cw.paramStartBuf.WriteByte(ch)
 | |
| 				break
 | |
| 			case secondeCsiChar:
 | |
| 				cw.paramStartBuf.WriteByte(ch)
 | |
| 				cw.state = secondCsiCode
 | |
| 				last = i - 1
 | |
| 			default:
 | |
| 				cw.resetBuffer()
 | |
| 				cw.state = outsideCsiCode
 | |
| 			}
 | |
| 		case secondCsiCode:
 | |
| 			if isParameterChar(ch) {
 | |
| 				cw.paramBuf.WriteByte(ch)
 | |
| 			} else {
 | |
| 				nw, err = cw.w.Write(p[first:last])
 | |
| 				r += nw
 | |
| 				if err != nil {
 | |
| 					return r, err
 | |
| 				}
 | |
| 				first = i + 1
 | |
| 				result := parseEscapeSequence(ch, cw.paramBuf.Bytes())
 | |
| 				if result == noConsole || (cw.mode == OutputNonColorEscSeq && result == unknown) {
 | |
| 					cw.paramBuf.WriteByte(ch)
 | |
| 					nw, err := cw.flushBuffer()
 | |
| 					if err != nil {
 | |
| 						return r, err
 | |
| 					}
 | |
| 					r += nw
 | |
| 				} else {
 | |
| 					n, _ := cw.resetBuffer()
 | |
| 					// Add one more to the size of the buffer for the last ch
 | |
| 					r += n + 1
 | |
| 				}
 | |
| 
 | |
| 				cw.state = outsideCsiCode
 | |
| 			}
 | |
| 		default:
 | |
| 			cw.state = outsideCsiCode
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if cw.mode != DiscardNonColorEscSeq || cw.state == outsideCsiCode {
 | |
| 		nw, err = cw.w.Write(p[first:])
 | |
| 		r += nw
 | |
| 	}
 | |
| 
 | |
| 	return r, err
 | |
| }
 |