-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdecorator.go
193 lines (175 loc) · 4.78 KB
/
decorator.go
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
// Copyright (c) 2017 Pronin S.V.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package fmtc
import (
"regexp"
"strings"
"unicode/utf8"
"golang.org/x/net/html"
)
const (
//CODERESET reset style after this code
CODERESET = "\033[0m"
)
// An array of tag and ASCI code matches for text
var fontStyle = map[string]string{
"b": "1", // <b> - Bold
"strong": "1", // <strong> - Bold
"u": "4", // <u> - Underline
"dim": "2", // <dim> - Dim
"reverse": "7", // <reverse> - Reverse
"blink": "5", // <blink> - Blink
"black": "30", // <black> - Black (Dark)
"red": "31", // <red> - Red
"green": "32", // <green> - Green
"yellow": "33", // <yellow> - Yellow
"blue": "34", // <blue> - Blue
"magenta": "35", // <magenta> - Magenta
"cyan": "36", // <cyan> - Cyan
"grey": "37", // <grey> - Grey (Smokey)
"white": "97", // <white> - White
}
// An array of tag and ASCI code matches for background colors
var background = map[string]string{
"black": "40", // Black (Dark)
"red": "41", // Red
"green": "42", // Green
"yellow": "43", // Yellow
"blue": "44", // Blue
"magenta": "45", // Magenta
"cyan": "46", // Cyan
"grey": "47", // Grey (Smokey)
"white": "107", // White
}
// LIFO stack struct fir HTML tags
type stack struct {
items []tag
}
// push add element to stack
func (stack *stack) push(value tag) {
stack.items = append(stack.items, value)
}
// pop delete element from stack
func (stack *stack) pop(tag tag) bool {
if len(stack.items) > 0 {
i := len(stack.items) - 1
if stack.items[i].name == tag.name {
stack.items = stack.items[:i]
return true
}
}
return false
}
// tag in this struct we store html-tag name and map of tag attributes
type tag struct {
name string
attr map[string]string
}
// decorate string using HTML tags from her
// It returns decorated string
func decorate(str string) string {
//get io.Reader from string
reader := strings.NewReader(str)
//get HTML Tokenizer from io.Reader
d := html.NewTokenizer(reader)
//Stack of unclosed HTML tags
tagsStack := stack{}
//Result string
finalString := ""
for {
tokenType := d.Next()
//if str is end, or if error
if tokenType == html.ErrorToken {
finalString += CODERESET
return finalString
}
//get current token of str
token := d.Token()
switch tokenType {
//if token type is StartTag (like <tagname>)
case html.StartTagToken:
oneTag := tag{name: token.Data}
//try to get tag attributes
if len(token.Attr) > 0 {
tagAttr := make(map[string]string)
for _, attr := range token.Attr {
tagAttr[attr.Key] = attr.Val
}
oneTag.attr = tagAttr
}
//try to get tag ANSI code
tagCode := getASCICode(oneTag)
if utf8.RuneCountInString(tagCode) > 0 {
//if ANSI code exists, then we add them to string
finalString += tagCode
//and add current tag in stack of opened tags
tagsStack.push(oneTag)
} else {
//if tag ANSI code not exists, then add to final string tag
finalString += token.String()
}
//if curent token is text
case html.TextToken:
//then add this text to final string
finalString += token.Data
//if current token is SelfClosingTag (like <br />)
case html.SelfClosingTagToken:
//add tag to string
finalString += token.String()
//if current token is EndTag (like </tagname>)
case html.EndTagToken:
oneTag := tag{name: token.Data}
//try to pop last tag from stack (Success if the last stack tag matches the current closing tag)
if tagsStack.pop(oneTag) == true {
finalString += CODERESET
finalString += applyOpenedTags(tagsStack)
} else {
finalString += token.String()
}
}
}
}
// decorateIface decorator for an empty interface
func decorateIface(a *[]interface{}) {
for i, x := range *a {
if s, ok := x.(string); ok {
(*a)[i] = decorate(s)
}
}
}
// applyOpenedTags apply to string not closed tagsArr
// It returns string with ASCI codes of not closet tags
func applyOpenedTags(tagsStack stack) string {
if len(tagsStack.items) == 0 {
return ""
}
tagsStr := ""
for _, tag := range tagsStack.items {
tagCode := getASCICode(tag)
if utf8.RuneCountInString(tagCode) > 0 {
tagsStr += tagCode
}
}
return tagsStr
}
// getASCICode get ASCI code of font style or background color
// It returns the code of current tag
func getASCICode(tag tag) string {
code := ""
var validShortBG = regexp.MustCompile(`^b_([A-Za-z]+)$`)
if tag.name == "bg" {
if color, ok := tag.attr["color"]; ok {
code = background[color]
}
} else if validShortBG.MatchString(tag.name) {
bgColor := validShortBG.FindStringSubmatch(tag.name)
code = background[bgColor[1]]
} else {
code = fontStyle[tag.name]
}
if utf8.RuneCountInString(code) > 0 {
code = "\033[" + code + "m"
}
return code
}