Skip to content

Commit ec87c80

Browse files
committed
feat(pkg/prettyprint): support colorizing terminal text
1 parent f900d8d commit ec87c80

2 files changed

Lines changed: 225 additions & 0 deletions

File tree

pkg/prettyprint/colorizer.go

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
// Package prettyprint contains tools for formatting text.
2+
package prettyprint
3+
4+
import (
5+
"bytes"
6+
"fmt"
7+
"strings"
8+
"text/template"
9+
)
10+
11+
// Colors contains a map of the standard ANSI color codes.
12+
//
13+
// There are four variants:
14+
// - Bare color names (Red, Black) color the characters.
15+
// - Bold color names add bolding to the characters.
16+
// - Under color names add underlining to the characters.
17+
// - Hi color names add highlighting (background colors).
18+
//
19+
// These can be used within `text/template` to provide colors. The convenience
20+
// function `Colorize()` provides this feature.
21+
var Colors = map[string]string{
22+
"Default": "\033[0m",
23+
"Black": "\033[0;30m",
24+
"Red": "\033[0;31m",
25+
"Green": "\033[0;32m",
26+
"Yellow": "\033[0;33m",
27+
"Blue": "\033[0;34m",
28+
"Purple": "\033[0;35m",
29+
"Cyan": "\033[0;36m",
30+
"White": "\033[0;37m",
31+
"BoldBlack": "\033[1;30m",
32+
"BoldRed": "\033[1;31m",
33+
"BoldGreen": "\033[1;32m",
34+
"BoldYellow": "\033[1;33m",
35+
"BoldBlue": "\033[1;34m",
36+
"BoldPurple": "\033[1;35m",
37+
"BoldCyan": "\033[1;36m",
38+
"BoldWhite": "\033[1;37m",
39+
"UnderBlack": "\033[4;30m",
40+
"UnderRed": "\033[4;31m",
41+
"UnderGreen": "\033[4;32m",
42+
"UnderYellow": "\033[4;33m",
43+
"UnderBlue": "\033[4;34m",
44+
"UnderPurple": "\033[4;35m",
45+
"UnderCyan": "\033[4;36m",
46+
"UnderWhite": "\033[4;37m",
47+
"HiBlack": "\033[30m",
48+
"HiRed": "\033[31m",
49+
"HiGreen": "\033[32m",
50+
"HiYellow": "\033[33m",
51+
"HiBlue": "\033[34m",
52+
"HiPurple": "\033[35m",
53+
"HiCyan": "\033[36m",
54+
"HiWhite": "\033[37m",
55+
"Deis1": "\033[31m● \033[34m▴ \033[32m■\033[0m",
56+
"Deis2": "\033[32m■ \033[31m● \033[34m▴\033[0m",
57+
"Deis3": "\033[34m▴ \033[32m■ \033[31m●\033[0m",
58+
"Deis": "\033[31m● \033[34m▴ \033[32m■\n\033[32m■ \033[31m● \033[34m▴\n\033[34m▴ \033[32m■ \033[31m●\n",
59+
}
60+
61+
// DeisIfy returns a pretty-printed deis logo along with the corresponding message
62+
func DeisIfy(msg string) string {
63+
var t = struct {
64+
Msg string
65+
C map[string]string
66+
}{
67+
Msg: msg,
68+
C: Colors,
69+
}
70+
tpl := "{{.C.Deis1}}\n{{.C.Deis2}} {{.Msg}}\n{{.C.Deis3}}\n"
71+
var buf bytes.Buffer
72+
template.Must(template.New("deis").Parse(tpl)).Execute(&buf, t)
73+
return buf.String()
74+
}
75+
76+
// Logo returns a colorized Deis logo with no space for text.
77+
func Logo() string {
78+
return Colorize("{{.Deis}}")
79+
}
80+
81+
// NoColor strips colors from the template.
82+
//
83+
// NoColor provides support for non-color ANSI terminals. It can be used
84+
// as an alternative to Colorize when it is detected that the terminal does
85+
// not support colors.
86+
func NoColor(msg string) string {
87+
empties := make(map[string]string, len(Colors))
88+
for k, _ := range Colors {
89+
empties[k] = ""
90+
}
91+
return colorize(msg, empties)
92+
}
93+
94+
// Colorize makes it easy to add colors to ANSI terminal output.
95+
//
96+
// This takes any of the colors defined in the Colors map. Colors are rendered
97+
// through the `text/template` system, so you may use pipes and functions as
98+
// well.
99+
//
100+
// Example:
101+
// Colorize("{{.Red}}ERROR:{{.Default}} Something happened.")
102+
func Colorize(msg string) string {
103+
return colorize(msg, Colors)
104+
}
105+
106+
// ColorizeVars provides template rendering with color support.
107+
//
108+
// The template is given a datum with two objects: `.V` and `.C`. `.V` contains
109+
// the `vars` passed into the function. `.C` contains the color map.
110+
//
111+
// Assuming `vars` contains a member named `Msg`, a template can be constructed
112+
// like this:
113+
// {{.C.Red}}Message:{{.C.Default}} .V.Msg
114+
func ColorizeVars(msg string, vars interface{}) string {
115+
var t = struct {
116+
V interface{}
117+
C map[string]string
118+
}{
119+
V: vars,
120+
C: Colors,
121+
}
122+
return colorize(msg, t)
123+
}
124+
125+
func colorize(msg string, vars interface{}) string {
126+
tpl, err := template.New(msg).Parse(msg)
127+
// If the template's not valid, we just ignore and return.
128+
if err != nil {
129+
return msg
130+
}
131+
var buf bytes.Buffer
132+
if err := tpl.Execute(&buf, vars); err != nil {
133+
return msg
134+
}
135+
136+
return buf.String()
137+
}
138+
139+
// Overwrite sends a line that will be replaced by a subsequent overwrite.
140+
//
141+
// Example:
142+
// Overwrite("foo")
143+
// Overwrite("bar")
144+
//
145+
// The above will print "foo" and then immediately replace it with "var".
146+
//
147+
// (Interpretation of \r is left to the shell.)
148+
func Overwrite(msg string) string {
149+
lm := len(msg)
150+
if lm >= 80 {
151+
return msg + "\r"
152+
}
153+
pad := 80 - len(msg)
154+
return msg + strings.Repeat(" ", pad) + "\r"
155+
156+
}
157+
158+
// Overwritef formats a string and then returns an overwrite line.
159+
//
160+
// See `Overwrite` for details.
161+
func Overwritef(msg string, args ...interface{}) string {
162+
return Overwrite(fmt.Sprintf(msg, args...))
163+
}

pkg/prettyprint/colorizer_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package prettyprint
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
"testing"
7+
)
8+
9+
func TestColorize(t *testing.T) {
10+
out := Colorize("{{.Red}}Hello {{.Default}}World{{.UnderGreen}}!{{.Default}}")
11+
expected := "\033[0;31mHello \033[0mWorld\033[4;32m!\033[0m"
12+
if out != expected {
13+
t.Errorf("Expected '%s', got '%s'", expected, out)
14+
}
15+
}
16+
17+
func TestColorizeVars(t *testing.T) {
18+
vars := map[string]string{"Who": "World"}
19+
tpl := "{{.C.Red}}Hello {{.C.Default}}{{.V.Who}}{{.C.UnderGreen}}!{{.C.Default}}"
20+
out := ColorizeVars(tpl, vars)
21+
expected := "\033[0;31mHello \033[0mWorld\033[4;32m!\033[0m"
22+
if out != expected {
23+
t.Errorf("Expected '%s', got '%s'", expected, out)
24+
}
25+
}
26+
27+
func TestNoColor(t *testing.T) {
28+
tpl := "{{.Red}}{{.Yellow}}{{.Green}}coffee all the things!{{.Default}}"
29+
expected := "coffee all the things!"
30+
out := NoColor(tpl)
31+
if out != expected {
32+
t.Errorf("Expected `%s`, got `%s`", expected, out)
33+
}
34+
}
35+
36+
func ExampleColorize() {
37+
out := Colorize("{{.Red}}Hello {{.Default}}World{{.UnderGreen}}!{{.Default}}")
38+
fmt.Println(out)
39+
}
40+
func ExampleColorizeVars() {
41+
vars := map[string]string{"Who": "World"}
42+
tpl := "{{.C.Red}}Hello {{.C.Default}}{{.V.Who}}!"
43+
out := ColorizeVars(tpl, vars)
44+
fmt.Println(out)
45+
}
46+
47+
func TestDeisIfy(t *testing.T) {
48+
d := DeisIfy("Test")
49+
if strings.Contains(d, "Deis1") {
50+
t.Errorf("Failed to compile template")
51+
}
52+
if !strings.Contains(d, "Test") {
53+
t.Errorf("Failed to render template")
54+
}
55+
}
56+
57+
func TestLogo(t *testing.T) {
58+
l := Logo()
59+
if l != Colors["Deis"] {
60+
t.Errorf("Expected \n%s\n, Got\n%s\n", Colors["Deis"], Logo())
61+
}
62+
}

0 commit comments

Comments
 (0)