1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204
|
package rgbterm
import (
"bufio"
"bytes"
"io"
"os"
"regexp"
"strconv"
"strings"
)
// MaxEscapeCodeLen is the maximum number of bytes that the contents of an escape
// code may be. If the escape code is longer than this, it is output verbatim
// without being replaced. If MaxEscapeCodeLen is set to 0 escape codes may be
// any length (checking is not performed)
//
// This is used to avoid a possible denial of service.
var MaxEscapeCodeLen uint = 15
// interpret reads from r and writes to w. While reading any color escape codes detected
// are replaced by the result of calling subst with the escape code.
func interpret(r io.ByteReader, w io.Writer, subst func(s string) []byte) (err error) {
inEscape := false
escape := &bytes.Buffer{}
out := bufio.NewWriter(w)
defer func() {
if err == nil {
err = out.Flush()
}
}()
for {
var c byte
c, err = r.ReadByte()
if err != nil {
// EOF. Don't consider this a failure of interpret()
err = nil
break
}
if inEscape {
if rune(c) == '{' && escape.Len() == 0 {
// False alarm: this was the sequence {{ which means the user wanted to
// output {.
_, err = out.Write([]byte("{"))
escape.Reset()
inEscape = false
} else if rune(c) == '}' {
_, err = out.Write(subst(escape.String()))
escape.Reset()
inEscape = false
} else {
escape.WriteByte(c)
if MaxEscapeCodeLen > 0 && uint(escape.Len()) > MaxEscapeCodeLen {
// Escape code too long
out.Write([]byte("{"))
_, err = out.Write(escape.Bytes())
inEscape = false
}
}
} else {
if rune(c) == '{' {
inEscape = true
} else {
_, err = out.Write([]byte{c})
}
}
if err != nil {
// Write error occurred
return
}
}
return
}
// colorRegex matches color escape codes
var colorRegex *regexp.Regexp = regexp.MustCompile(`#([[:xdigit:]]{2})([[:xdigit:]]{2})([[:xdigit:]]{2})`)
func parseEscape(s string) ([]uint8, []uint8) {
parts := strings.Split(s, ",")
atoi := func(s []string) (r []uint8) {
r = make([]uint8, len(s))
for i, v := range s {
i64, _ := strconv.ParseInt(v, 16, 0)
r[i] = uint8(i64)
}
return
}
escapes := make([][]uint8, 2)
for i, p := range parts {
match := colorRegex.FindStringSubmatch(p)
if match != nil {
escapes[i] = atoi(match[1:])
}
}
return escapes[0], escapes[1]
}
func substColor(s string) (c []byte) {
fg, bg := parseEscape(s)
if fg == nil && bg == nil {
// Reset colors to default
return reset
}
if fg != nil {
c = append(c, color(fg[0], fg[1], fg[2], true)...)
}
if bg != nil {
if len(c) > 0 {
c = append(c, byte(';'))
}
c = append(c, color(bg[0], bg[1], bg[2], false)...)
}
c = append(before, c...)
c = append(c, after...)
return
}
// Interpret reads data from r and writes it to w,
// while substituting the color escapes in the input.
//
// Color escapes are directives having one of forms
// on the following lines:
//
// {#RRGGBB}
// {#RRGGBB,#RRGGBB}
// {,#RRGGBB}
// {}
//
// The first form sets the terminal foreground color to
// the color RR,GG,BB where RR is the red component,
// GG green and BB blue. Each component is in hex.
//
// The second form sets the foreground and background
// colors, while the third sets only the background.
//
// The fourth form resets the terminal colors to defaults.
//
// To output a literal { character, use two braces: {{.
func Interpret(r io.ByteReader, w io.Writer) error {
return interpret(r, w, substColor)
}
// ColorizeTemplate substitutes the color escapes in the
// string s and returns the resulting string.
//
// See Interpret for a description of color escapes.
func InterpretStr(s string) string {
var out bytes.Buffer
Interpret(bytes.NewBuffer([]byte(s)), &out)
return out.String()
}
// ColorTemplateWriter writes to an underlying io.Writer
// while substituting the color escapes in the input.
//
// See Interpret for a description of color escapes.
type InterpretingWriter struct {
p *io.PipeWriter
}
// NewColorTemplateWriter creates a ColorTemplateWriter that writes to
// w while substituting the color escapes in the input.
//
// See Interpret for a description of color escapes.
func NewInterpretingWriter(w io.Writer) InterpretingWriter {
pr, pw := io.Pipe()
go Interpret(bufio.NewReader(pr), w)
return InterpretingWriter{p: pw}
}
// Write writes to the underlying io.Writer while substituting the
// color escapes in the input.
//
// See Interpret for a description of color escapes.
func (tw InterpretingWriter) Write(p []byte) (n int, err error) {
return tw.p.Write(p)
}
var (
// ColorOut is a io.Writer that writes to os.Stdout
// while substituting the the color escapes in it's input.
//
// See Interpret for a description of color escapes.
ColorOut io.Writer = NewInterpretingWriter(os.Stdout)
// ColorErr is a io.Writer that writes to os.Stderr
// while substituting the the color escapes in it's input.
//
// See Interpret for a description of color escapes.
ColorErr io.Writer = NewInterpretingWriter(os.Stderr)
)
|