mirror of
https://github.com/astaxie/beego.git
synced 2024-11-16 17:00:54 +00:00
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
|
|
}
|