summaryrefslogtreecommitdiff
path: root/internal/color
diff options
context:
space:
mode:
authorPaul Buetow <pbuetow@mimecast.com>2021-10-21 21:28:49 +0300
committerPaul Buetow <pbuetow@mimecast.com>2021-10-21 21:28:49 +0300
commitf4207a55f71bfbcfdc532d5cdd3befaa3474a157 (patch)
treeea5e4a2d2a67035f645bdee496ae55a52034178a /internal/color
parentd80d6070557e3a800e3a54967af9eced518f116b (diff)
parent739205206d63bf42f4e843b39d04d4c8cd8207c3 (diff)
merge develop
Diffstat (limited to 'internal/color')
-rw-r--r--internal/color/brush/brush.go194
-rw-r--r--internal/color/color.go174
-rw-r--r--internal/color/color_test.go53
-rw-r--r--internal/color/colorfy.go56
-rw-r--r--internal/color/paint.go91
-rw-r--r--internal/color/table.go53
6 files changed, 517 insertions, 104 deletions
diff --git a/internal/color/brush/brush.go b/internal/color/brush/brush.go
new file mode 100644
index 0000000..63d63d8
--- /dev/null
+++ b/internal/color/brush/brush.go
@@ -0,0 +1,194 @@
+package brush
+
+import (
+ "strings"
+
+ "github.com/mimecast/dtail/internal/color"
+ "github.com/mimecast/dtail/internal/config"
+ "github.com/mimecast/dtail/internal/io/pool"
+ "github.com/mimecast/dtail/internal/protocol"
+)
+
+func paintSeverity(sb *strings.Builder, text string) bool {
+ switch {
+ case strings.HasPrefix(text, "WARN"):
+ color.PaintWithAttr(sb, text,
+ config.Client.TermColors.Common.SeverityWarnFg,
+ config.Client.TermColors.Common.SeverityWarnBg,
+ config.Client.TermColors.Common.SeverityWarnAttr)
+
+ case strings.HasPrefix(text, "ERROR"):
+ color.PaintWithAttr(sb, text,
+ config.Client.TermColors.Common.SeverityErrorFg,
+ config.Client.TermColors.Common.SeverityErrorBg,
+ config.Client.TermColors.Common.SeverityErrorAttr)
+
+ case strings.HasPrefix(text, "FATAL"):
+ color.PaintWithAttr(sb, text,
+ config.Client.TermColors.Common.SeverityFatalFg,
+ config.Client.TermColors.Common.SeverityFatalBg,
+ config.Client.TermColors.Common.SeverityFatalAttr)
+
+ default:
+ return false
+ }
+ return true
+}
+
+func paintRemote(sb *strings.Builder, line string) {
+ splitted := strings.SplitN(line, protocol.FieldDelimiter, 6)
+
+ color.PaintWithAttr(sb, splitted[0],
+ config.Client.TermColors.Remote.RemoteFg,
+ config.Client.TermColors.Remote.RemoteBg,
+ config.Client.TermColors.Remote.RemoteAttr)
+
+ color.PaintWithAttr(sb, protocol.FieldDelimiter,
+ config.Client.TermColors.Remote.DelimiterFg,
+ config.Client.TermColors.Remote.DelimiterBg,
+ config.Client.TermColors.Remote.DelimiterAttr)
+
+ color.PaintWithAttr(sb, splitted[1],
+ config.Client.TermColors.Remote.HostnameFg,
+ config.Client.TermColors.Remote.HostnameBg,
+ config.Client.TermColors.Remote.HostnameAttr)
+
+ color.PaintWithAttr(sb, protocol.FieldDelimiter,
+ config.Client.TermColors.Remote.DelimiterFg,
+ config.Client.TermColors.Remote.DelimiterBg,
+ config.Client.TermColors.Remote.DelimiterAttr)
+
+ if splitted[2] == "100" {
+ color.PaintWithAttr(sb, splitted[2],
+ config.Client.TermColors.Remote.StatsOkFg,
+ config.Client.TermColors.Remote.StatsOkBg,
+ config.Client.TermColors.Remote.StatsOkAttr)
+ } else {
+ color.PaintWithAttr(sb, splitted[2],
+ config.Client.TermColors.Remote.StatsWarnFg,
+ config.Client.TermColors.Remote.StatsWarnBg,
+ config.Client.TermColors.Remote.StatsWarnAttr)
+ }
+
+ color.PaintWithAttr(sb, protocol.FieldDelimiter,
+ config.Client.TermColors.Remote.DelimiterFg,
+ config.Client.TermColors.Remote.DelimiterBg,
+ config.Client.TermColors.Remote.DelimiterAttr)
+
+ color.PaintWithAttr(sb, splitted[3],
+ config.Client.TermColors.Remote.CountFg,
+ config.Client.TermColors.Remote.CountBg,
+ config.Client.TermColors.Remote.CountAttr)
+
+ color.PaintWithAttr(sb, protocol.FieldDelimiter,
+ config.Client.TermColors.Remote.DelimiterFg,
+ config.Client.TermColors.Remote.DelimiterBg,
+ config.Client.TermColors.Remote.DelimiterAttr)
+
+ color.PaintWithAttr(sb, splitted[4],
+ config.Client.TermColors.Remote.IDFg,
+ config.Client.TermColors.Remote.IDBg,
+ config.Client.TermColors.Remote.IDAttr)
+ color.PaintWithAttr(sb, protocol.FieldDelimiter,
+ config.Client.TermColors.Remote.DelimiterFg,
+ config.Client.TermColors.Remote.DelimiterBg,
+ config.Client.TermColors.Remote.DelimiterAttr)
+
+ if paintSeverity(sb, splitted[5]) {
+ return
+ }
+ color.PaintWithAttr(sb, splitted[5],
+ config.Client.TermColors.Remote.TextFg,
+ config.Client.TermColors.Remote.TextBg,
+ config.Client.TermColors.Remote.TextAttr)
+}
+
+func paintClient(sb *strings.Builder, line string) {
+ splitted := strings.SplitN(line, protocol.FieldDelimiter, 3)
+
+ color.PaintWithAttr(sb, splitted[0],
+ config.Client.TermColors.Client.ClientFg,
+ config.Client.TermColors.Client.ClientBg,
+ config.Client.TermColors.Client.ClientAttr)
+
+ color.PaintWithAttr(sb, protocol.FieldDelimiter,
+ config.Client.TermColors.Client.DelimiterFg,
+ config.Client.TermColors.Client.DelimiterBg,
+ config.Client.TermColors.Client.DelimiterAttr)
+
+ color.PaintWithAttr(sb, splitted[1],
+ config.Client.TermColors.Client.HostnameFg,
+ config.Client.TermColors.Client.HostnameBg,
+ config.Client.TermColors.Client.HostnameAttr)
+
+ color.PaintWithAttr(sb, protocol.FieldDelimiter,
+ config.Client.TermColors.Client.DelimiterFg,
+ config.Client.TermColors.Client.DelimiterBg,
+ config.Client.TermColors.Client.DelimiterAttr)
+
+ if paintSeverity(sb, splitted[2]) {
+ return
+ }
+
+ color.PaintWithAttr(sb, splitted[2],
+ config.Client.TermColors.Client.TextFg,
+ config.Client.TermColors.Client.TextBg,
+ config.Client.TermColors.Client.TextAttr)
+}
+
+func paintServer(sb *strings.Builder, line string) {
+ splitted := strings.SplitN(line, protocol.FieldDelimiter, 3)
+
+ color.PaintWithAttr(sb, splitted[0],
+ config.Client.TermColors.Server.ServerFg,
+ config.Client.TermColors.Server.ServerBg,
+ config.Client.TermColors.Server.ServerAttr)
+
+ color.PaintWithAttr(sb, protocol.FieldDelimiter,
+ config.Client.TermColors.Server.DelimiterFg,
+ config.Client.TermColors.Server.DelimiterBg,
+ config.Client.TermColors.Server.DelimiterAttr)
+
+ color.PaintWithAttr(sb, splitted[1],
+ config.Client.TermColors.Server.HostnameFg,
+ config.Client.TermColors.Server.HostnameBg,
+ config.Client.TermColors.Server.HostnameAttr)
+
+ color.PaintWithAttr(sb, protocol.FieldDelimiter,
+ config.Client.TermColors.Server.DelimiterFg,
+ config.Client.TermColors.Server.DelimiterBg,
+ config.Client.TermColors.Server.DelimiterAttr)
+
+ if paintSeverity(sb, splitted[2]) {
+ return
+ }
+
+ color.PaintWithAttr(sb, splitted[2],
+ config.Client.TermColors.Server.TextFg,
+ config.Client.TermColors.Server.TextBg,
+ config.Client.TermColors.Server.TextAttr)
+}
+
+// Colorfy a given line based on the line's content.
+func Colorfy(line string) string {
+ sb := pool.BuilderBuffer.Get().(*strings.Builder)
+ defer pool.RecycleBuilderBuffer(sb)
+
+ switch {
+ case strings.HasPrefix(line, "REMOTE"):
+ paintRemote(sb, line)
+
+ case strings.HasPrefix(line, "CLIENT"):
+ paintClient(sb, line)
+
+ case strings.HasPrefix(line, "SERVER"):
+ paintServer(sb, line)
+
+ default:
+ color.PaintWithAttr(sb, line,
+ color.FgDefault,
+ color.BgDefault,
+ color.AttrNone)
+ }
+ return sb.String()
+}
diff --git a/internal/color/color.go b/internal/color/color.go
index 0736199..9d0bc2e 100644
--- a/internal/color/color.go
+++ b/internal/color/color.go
@@ -1,70 +1,148 @@
-// Package color is used to prettify console output via ANSII terminal colors.
+// Package color contains all terminal color codes we know of.
package color
import (
"fmt"
+ "strings"
)
-// Color name.
-type Color string
+// FgColor is the text foreground color.
+type FgColor string
-// Attribute of a color.
+// BgColor is the text background color.
+type BgColor string
+
+// Attribute of text.
type Attribute string
// The possible color variations.
const (
- escape = "\x1b"
- reset = escape + "[0m"
- seq string = "%s%s%s"
+ escape = "\x1b"
- Gray Color = escape + "[30m"
- Red Color = escape + "[31m"
- Green Color = escape + "[32m"
- Orange Color = escape + "[33m"
- Blue Color = escape + "[34m"
- Magenta Color = escape + "[35m"
- Yellow Color = escape + "[36m"
- LightGray Color = escape + "[37m"
+ FgBlack FgColor = escape + "[30m"
+ FgRed FgColor = escape + "[31m"
+ FgGreen FgColor = escape + "[32m"
+ FgYellow FgColor = escape + "[33m"
+ FgBlue FgColor = escape + "[34m"
+ FgMagenta FgColor = escape + "[35m"
+ FgCyan FgColor = escape + "[36m"
+ FgWhite FgColor = escape + "[37m"
+ FgDefault FgColor = escape + "[39m"
- BgGray Color = escape + "[40m"
- BgRed Color = escape + "[41m"
- BgGreen Color = escape + "[42m"
- BgOrange Color = escape + "[43m"
- BgBlue Color = escape + "[44m"
- BgMagenta Color = escape + "[45m"
- BgYellow Color = escape + "[46m"
- BgLightGray Color = escape + "[47m"
+ BgBlack BgColor = escape + "[40m"
+ BgRed BgColor = escape + "[41m"
+ BgGreen BgColor = escape + "[42m"
+ BgYellow BgColor = escape + "[43m"
+ BgBlue BgColor = escape + "[44m"
+ BgMagenta BgColor = escape + "[45m"
+ BgCyan BgColor = escape + "[46m"
+ BgWhite BgColor = escape + "[47m"
+ BgDefault BgColor = escape + "[49m"
- Bold Attribute = escape + "[1m"
- Italic Attribute = escape + "[3m"
- Underline Attribute = escape + "[4m"
- ReverseColor Attribute = escape + "[7m"
+ AttrNone Attribute = ""
+ AttrReset Attribute = escape + "[0m"
+ AttrBold Attribute = escape + "[1m"
+ AttrDim Attribute = escape + "[2m"
+ AttrItalic Attribute = escape + "[3m"
+ AttrUnderline Attribute = escape + "[4m"
+ AttrBlink Attribute = escape + "[5m"
+ AttrSlowBlink Attribute = escape + "[5m"
+ AttrRapidBlink Attribute = escape + "[6m"
+ AttrReverse Attribute = escape + "[7m"
+ AttrHidden Attribute = escape + "[8m"
+)
- resetBold = escape + "[22m"
- resetItalic = escape + "[23m"
- resetUnderline = escape + "[24m"
+// ColorNames is the list of all supported terminal colors.
+var ColorNames = []string{
+ "Black", "Red", "Green", "Yellow", "Blue", "Magenta", "Cyan", "White", "Default",
+}
- Test Color = BgYellow
- TestAttr Attribute = Bold
-)
+// AttributeNames is the list of all supported terminal text attributes.
+var AttributeNames = []string{
+ "Bold", "Dim", "Italic", "Underline", "Blink", "SlowBlink", "RapidBlink",
+ "Reverse", "Hidden", "None",
+}
-// Colored DTail client output enabled.
-var Colored bool
+// ToFgColor converts a given string (e.g. from a config file) into a foreground
+// color code.
+func ToFgColor(s string) (FgColor, error) {
+ switch strings.ToLower(s) {
+ case "black":
+ return FgBlack, nil
+ case "red":
+ return FgRed, nil
+ case "green":
+ return FgGreen, nil
+ case "yellow":
+ return FgYellow, nil
+ case "blue":
+ return FgBlue, nil
+ case "magenta":
+ return FgMagenta, nil
+ case "cyan":
+ return FgCyan, nil
+ case "white":
+ return FgWhite, nil
+ case "default":
+ return FgDefault, nil
+ default:
+ return FgDefault, fmt.Errorf("unknown foreground text color '" + s + "'")
+ }
+}
-// Paint a given string in a given color.
-func Paint(c Color, s string) string {
- return fmt.Sprintf(seq, c, s, reset)
+// ToBgColor converts a given string (e.g. from a config file) into a background
+// color code.
+func ToBgColor(s string) (BgColor, error) {
+ switch strings.ToLower(s) {
+ case "black":
+ return BgBlack, nil
+ case "red":
+ return BgRed, nil
+ case "green":
+ return BgGreen, nil
+ case "yellow":
+ return BgYellow, nil
+ case "blue":
+ return BgBlue, nil
+ case "magenta":
+ return BgMagenta, nil
+ case "cyan":
+ return BgCyan, nil
+ case "white":
+ return BgWhite, nil
+ case "default":
+ return BgDefault, nil
+ default:
+ return BgDefault, fmt.Errorf("unknown background text color '" + s + "'")
+ }
}
-// Attr adds a given attribute to a given string, such as "bold" or "italic".
-func Attr(c Attribute, s string) string {
- switch c {
- case Bold:
- return fmt.Sprintf(seq, Bold, s, resetBold)
- case Italic:
- return fmt.Sprintf(seq, Italic, s, resetItalic)
- case Underline:
- return fmt.Sprintf(seq, Underline, s, resetUnderline)
+// ToAttribute converts a given string (e.g. from a config file) into a text attribute.
+func ToAttribute(s string) (Attribute, error) {
+ switch strings.ToLower(s) {
+ case "bold":
+ return AttrBold, nil
+ case "dim":
+ return AttrDim, nil
+ case "italic":
+ return AttrItalic, nil
+ case "underline":
+ return AttrUnderline, nil
+ case "blink":
+ return AttrBlink, nil
+ case "slowblink":
+ return AttrSlowBlink, nil
+ case "rapidblink":
+ return AttrRapidBlink, nil
+ case "reverse":
+ return AttrReverse, nil
+ case "hidden":
+ return AttrHidden, nil
+ case "none":
+ fallthrough
+ case "":
+ return AttrNone, nil
+ default:
+ return AttrNone, fmt.Errorf("unknown text attribute '" + s + "'")
}
- panic("Unknown attribute")
}
diff --git a/internal/color/color_test.go b/internal/color/color_test.go
new file mode 100644
index 0000000..7002052
--- /dev/null
+++ b/internal/color/color_test.go
@@ -0,0 +1,53 @@
+package color
+
+import (
+ "strings"
+ "testing"
+)
+
+func TestColors(t *testing.T) {
+ text := " Mimecast "
+ builder := strings.Builder{}
+
+ for _, color := range ColorNames {
+ fgColor, err := ToFgColor(color)
+ if err != nil {
+ t.Errorf("unable to paint foreground : %s\n%v", text, err)
+ }
+ builder.WriteString(PaintStrFg(text, fgColor))
+
+ bgColor, err := ToBgColor(color)
+ if err != nil {
+ t.Errorf("unable to paint background: %s\n%v", text, err)
+ }
+ builder.WriteString(PaintStrBg(text, bgColor))
+ }
+
+ for _, fg := range ColorNames {
+ fgColor, _ := ToFgColor(fg)
+ for _, bg := range ColorNames {
+ if fg == bg {
+ continue
+ }
+ bgColor, _ := ToBgColor(bg)
+ builder.WriteString(PaintStr(text, fgColor, bgColor))
+ }
+ }
+
+ t.Log(builder.String())
+}
+
+func TestAttributes(t *testing.T) {
+ text := " Mimecast "
+ builder := strings.Builder{}
+
+ for _, attribute := range AttributeNames {
+ att, err := ToAttribute(attribute)
+ if err != nil {
+ t.Errorf("unable to paint attribute: %s\n%v", text, err)
+ }
+ builder.WriteString(PaintStrWithAttr(text, FgWhite, BgBlue, att))
+ }
+
+ t.Log(builder.String())
+}
diff --git a/internal/color/colorfy.go b/internal/color/colorfy.go
deleted file mode 100644
index a2beb7a..0000000
--- a/internal/color/colorfy.go
+++ /dev/null
@@ -1,56 +0,0 @@
-package color
-
-import (
- "fmt"
- "strings"
-)
-
-// Add some color to log lines received from remote servers.
-func paintRemote(line string) string {
- splitted := strings.Split(line, "|")
- if splitted[2] == "100" {
- splitted[2] = Paint(BgGreen, splitted[2])
- } else {
- splitted[2] = Paint(BgRed, splitted[2])
- }
- info := strings.Join(splitted[0:5], "|")
- log := strings.Join(splitted[5:], "|")
-
- if strings.HasPrefix(log, "WARN") {
- log = Paint(BgYellow, log)
- } else if strings.HasPrefix(log, "ERROR") {
- log = Paint(BgRed, log)
- } else if strings.HasPrefix(log, "FATAL") {
- log = Attr(Bold, Paint(BgRed, log))
- } else {
- log = Paint(Blue, log)
- }
-
- return fmt.Sprintf("%s|%s", info, log)
-}
-
-// Add some color to stats generated by the client.
-func paintClientStats(line string) string {
- splitted := strings.Split(line, "|")
- first := strings.Join(splitted[0:4], "|")
- connected := Paint(BgBlue, splitted[4])
- last := strings.Join(splitted[5:], "|")
-
- return fmt.Sprintf("%s|%s|%s", first, connected, last)
-}
-
-// Colorfy a given line based on the line's content.
-func Colorfy(line string) string {
- switch {
- case strings.HasPrefix(line, "REMOTE"):
- return paintRemote(line)
- case strings.HasPrefix(line, "CLIENT") && strings.Contains(line, "|stats|"):
- return paintClientStats(line)
- case strings.Contains(line, "ERROR"):
- return Paint(Magenta, line)
- case strings.Contains(line, "WARN"):
- return Paint(Magenta, line)
- }
-
- return line
-}
diff --git a/internal/color/paint.go b/internal/color/paint.go
new file mode 100644
index 0000000..7735d87
--- /dev/null
+++ b/internal/color/paint.go
@@ -0,0 +1,91 @@
+package color
+
+import (
+ "fmt"
+ "strings"
+)
+
+// PaintStr paints a given text in a given foreground/background color combination.
+func PaintStr(text string, fg FgColor, bg BgColor) string {
+ return fmt.Sprintf("%s%s%s%s%s", fg, bg, text, BgDefault, FgDefault)
+}
+
+// PaintStrWithAttr paints a given text in a given foreground/background/attribute
+// combination
+func PaintStrWithAttr(text string, fg FgColor, bg BgColor, attr Attribute) string {
+ if attr == AttrNone {
+ return PaintStr(text, fg, bg)
+ }
+ return fmt.Sprintf("%s%s%s%s%s%s%s", fg, bg, attr, text, AttrReset,
+ BgDefault, FgDefault)
+}
+
+// PaintStrFg paints a given text in a given foreground color.
+func PaintStrFg(text string, fg FgColor) string {
+ return fmt.Sprintf("%s%s%s", fg, text, FgDefault)
+}
+
+// PaintStrBg paints a given text in a given background color.
+func PaintStrBg(text string, bg BgColor) string {
+ return fmt.Sprintf("%s%s%s", bg, text, BgDefault)
+}
+
+// PaintStrAttr adds a given attribute to a given text, such as "bold" or "italic".
+func PaintStrAttr(text string, attr Attribute) string {
+ return fmt.Sprintf("%s%s%s", attr, text, AttrReset)
+}
+
+// Paint paints a given text in a given foreground/background color combination.
+func Paint(sb *strings.Builder, text string, fg FgColor, bg BgColor) {
+ sb.WriteString(string(fg))
+ sb.WriteString(string(bg))
+ sb.WriteString(text)
+ sb.WriteString(string(BgDefault))
+ sb.WriteString(string(FgDefault))
+}
+
+// Reset background and foreground colors.
+func Reset(sb *strings.Builder) {
+ sb.WriteString(string(BgDefault))
+ sb.WriteString(string(FgDefault))
+}
+
+// PaintWithAttr starts painting a given text in a given foreground/background/
+// attribute combination.
+func PaintWithAttr(sb *strings.Builder, text string, fg FgColor, bg BgColor,
+ attr Attribute) {
+
+ if attr == AttrNone {
+ Paint(sb, text, fg, bg)
+ return
+ }
+ sb.WriteString(string(fg))
+ sb.WriteString(string(bg))
+ sb.WriteString(string(attr))
+ sb.WriteString(text)
+ sb.WriteString(string(AttrReset))
+ sb.WriteString(string(BgDefault))
+ sb.WriteString(string(FgDefault))
+}
+
+// PaintWithAttrs is similar to PaintWithAttr, but it takes multiple attributes.
+func PaintWithAttrs(sb *strings.Builder, text string, fg FgColor, bg BgColor,
+ attrs []Attribute) {
+
+ sb.WriteString(string(fg))
+ sb.WriteString(string(bg))
+ for _, attr := range attrs {
+ sb.WriteString(string(attr))
+ }
+ sb.WriteString(text)
+ sb.WriteString(string(AttrReset))
+ sb.WriteString(string(BgDefault))
+ sb.WriteString(string(FgDefault))
+}
+
+// ResetWithAttr resets background, foreground and attributes.
+func ResetWithAttr(sb *strings.Builder) {
+ sb.WriteString(string(AttrReset))
+ sb.WriteString(string(BgDefault))
+ sb.WriteString(string(FgDefault))
+}
diff --git a/internal/color/table.go b/internal/color/table.go
new file mode 100644
index 0000000..e0e4946
--- /dev/null
+++ b/internal/color/table.go
@@ -0,0 +1,53 @@
+package color
+
+import (
+ "fmt"
+ "os"
+)
+
+const sampleParagraph string = "Mimecast is Making Email Safer for Business. " +
+ "We believe that securely operating a business in the cloud requires new " +
+ "levels of IT preparedness, centered around cyber resilience. This is why " +
+ "we unify the delivery and management of security, continuity and data " +
+ "protection for email via one, simple-to-use cloud platform. Thousands of " +
+ "organizations trust us to increase their cyber resilience preparedness, " +
+ "streamline compliance, reduce IT complexity and keep their business running. " +
+ "We give employees fast and secure access to sensitive business information, " +
+ "and ensure email keeps flowing in the event of an outage. Mimecast will " +
+ "remain committed to protecting your IT assets through constant innovation " +
+ "and focus on your success."
+
+// TablePrintAndExit prints the color table and then exits the process.
+func TablePrintAndExit(displaySampleParagraph bool) {
+ for _, attr := range AttributeNames {
+ if attr == "Hidden" || attr == "SlowBlink" {
+ continue
+ }
+ printColorTable(attr, displaySampleParagraph)
+ }
+ os.Exit(0)
+}
+
+func printColorTable(attr string, displaySampleParagraph bool) {
+ for _, fg := range ColorNames {
+ fgColor, _ := ToFgColor(fg)
+ for _, bg := range ColorNames {
+ if fg == bg {
+ continue
+ }
+
+ bgColor, _ := ToBgColor(bg)
+ attribute, _ := ToAttribute(attr)
+ text := fmt.Sprintf(" Foreground:%10s | Background:%10s | Attribute:%10s ",
+ fg, bg, attr)
+ fmt.Print(PaintStrWithAttr(text, fgColor, bgColor, attribute))
+
+ if displaySampleParagraph {
+ fmt.Print("\n")
+ fmt.Print(PaintStrWithAttr(sampleParagraph, fgColor, bgColor, attribute))
+ fmt.Print("\n")
+ }
+ fmt.Print("\n")
+ }
+ }
+}