-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
colors.go
164 lines (136 loc) 路 5.66 KB
/
colors.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
// Copyright 2016-2018, Pulumi Corporation.
//
// 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.
package colors
import (
"fmt"
"strings"
"github.com/reconquest/loreley"
"github.com/pulumi/pulumi/pkg/util/contract"
)
const colorLeft = "<{%"
const colorRight = "%}>"
func init() {
// Change the Loreley delimiters from { and }, to something more complex, to avoid accidental collisions.
loreley.DelimLeft = colorLeft
loreley.DelimRight = colorRight
}
func Command(s string) string {
return colorLeft + s + colorRight
}
// TrimPartialCommand returns the input string with any partial colorization command trimmed off of the right end of
// the string.
func TrimPartialCommand(s string) string {
// First check for a partial left delimiter at the end of the string.
partialDelimLeft := colorLeft
if len(partialDelimLeft) > len(s) {
partialDelimLeft = partialDelimLeft[:len(s)]
}
for len(partialDelimLeft) > 0 {
trailer := s[len(s)-len(partialDelimLeft):]
if trailer == partialDelimLeft {
return s[:len(s)-len(partialDelimLeft)]
}
partialDelimLeft = partialDelimLeft[:len(partialDelimLeft)-1]
}
// Next check for a complete left delimiter. If there no complete left delimiter, just return the string as-is.
lastDelimLeft := strings.LastIndex(s, colorLeft)
if lastDelimLeft == -1 {
return s
}
// If there is a complete left delimiter, look for a matching complete right delimiter. If there is a match, return
// the string as-is.
if strings.Contains(s[lastDelimLeft:], colorRight) {
return s
}
// Otherwise, return the string up to but not including the incomplete left delimiter.
return s[:lastDelimLeft]
}
func Colorize(s fmt.Stringer) string {
txt := s.String()
return colorizeText(txt)
}
func colorizeText(s string) string {
style, err := loreley.Compile(s, nil /*extensions*/)
contract.Assertf(err == nil, "Expected no errors during string colorization; str=%v, err=%v", s, err)
// Note: we only get into this codepath if we determined colors should be on. This was either
// because we were in color=auto mode and our env detection determined colors were reasonable to
// have, or because we're in color=always mode.
//
// However, Loreley does its own tty detection and disables colors if there is no tty. We don't
// want that behavior if we've determined colors should be on. So we explicitly override their
// automatic detection.
style.NoColors = false
result, err := style.ExecuteToString(nil /*data*/)
contract.Assertf(err == nil, "Expected no errors during string colorization; str=%v, err=%v", s, err)
return result
}
// Highlight takes an input string, a sequence of commands, and replaces all occurrences of that string with
// a "highlighted" version surrounded by those commands and a final reset afterwards.
func Highlight(s, text, commands string) string {
return strings.Replace(s, text, commands+text+Reset, -1)
}
var (
Reset = Command("reset")
Bold = Command("bold")
Underline = Command("underline")
)
// Basic colors.
var (
Red = Command("fg 1")
Green = Command("fg 2")
Yellow = Command("fg 3")
Blue = Command("fg 4")
Magenta = Command("fg 5")
Cyan = Command("fg 6")
BrightRed = Command("fg 9")
BrightGreen = Command("fg 10")
BrightBlue = Command("fg 12")
BrightMagenta = Command("fg 13")
BrightCyan = Command("fg 14")
RedBackground = Command("bg 1")
GreenBackground = Command("bg 2")
YellowBackground = Command("bg 3")
BlueBackground = Command("bg 4")
// We explicitly do not expose blacks/whites. They're problematic given that we don't know what
// terminal settings the user has. Best to avoid them and not run into contrast problems.
Black = Command("fg 0") // Only use with background colors.
// White = Command("fg 7")
// BrightBlack = Command("fg 8")
// BrightYellow = Command("fg 11")
// BrightWhite = Command("fg 15")
)
// Special predefined colors for logical conditions.
var (
SpecImportant = Yellow // for particularly noteworthy messages.
// for notes that can be skimmed or aren't very important. Just use the standard terminal text
// color.
SpecUnimportant = Reset
SpecDebug = SpecUnimportant // for debugging.
SpecInfo = Magenta // for information.
SpecError = Red // for errors.
SpecWarning = Yellow // for warnings.
SpecHeadline = BrightMagenta + Bold // for headings in the CLI.
SpecPrompt = Cyan + Bold // for prompting the user
SpecAttention = BrightRed // for messages that are meant to grab attention.
// for simple notes. Just use the standard terminal text color.
SpecNote = Reset
SpecCreate = Green // for adds (in the diff sense).
SpecUpdate = Yellow // for changes (in the diff sense).
SpecReplace = BrightMagenta // for replacements (in the diff sense).
SpecDelete = Red // for deletes (in the diff sense).
SpecCreateReplacement = BrightGreen // for replacement creates (in the diff sense).
SpecDeleteReplaced = BrightRed // for replacement deletes (in the diff sense).
// for reads (relatively unimportant). Just use the standard terminal text color.
SpecRead = Reset
)