From bc4f12bea9e35d73c5d7ce5e991f47d88c809400 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Mon, 6 Feb 2023 14:38:19 -0500 Subject: [PATCH] ref(color): simplify the color interface The `Sequence` function doesn't need to have a bg argument, this should be the job of the styler to apply bg specific modifications. Make `ANSIColor` into a struct with a `bg` flag. --- ansicolors.go | 34 ++++++++++++------------ color.go | 60 +++++++++++++++++++++++------------------ profile.go | 2 +- style.go | 20 ++++++++++++-- termenv_other.go | 4 +-- termenv_test.go | 66 +++++++++++++++++++++++----------------------- termenv_unix.go | 8 +++--- termenv_windows.go | 4 +-- 8 files changed, 111 insertions(+), 87 deletions(-) diff --git a/ansicolors.go b/ansicolors.go index ee303e2..b8cede6 100644 --- a/ansicolors.go +++ b/ansicolors.go @@ -1,23 +1,23 @@ package termenv // ANSI color codes -const ( - ANSIBlack ANSIColor = iota - ANSIRed - ANSIGreen - ANSIYellow - ANSIBlue - ANSIMagenta - ANSICyan - ANSIWhite - ANSIBrightBlack - ANSIBrightRed - ANSIBrightGreen - ANSIBrightYellow - ANSIBrightBlue - ANSIBrightMagenta - ANSIBrightCyan - ANSIBrightWhite +var ( + ANSIBlack = ANSIColor{Color: 0} + ANSIRed = ANSIColor{Color: 1} + ANSIGreen = ANSIColor{Color: 2} + ANSIYellow = ANSIColor{Color: 3} + ANSIBlue = ANSIColor{Color: 4} + ANSIMagenta = ANSIColor{Color: 5} + ANSICyan = ANSIColor{Color: 6} + ANSIWhite = ANSIColor{Color: 7} + ANSIBrightBlack = ANSIColor{Color: 8} + ANSIBrightRed = ANSIColor{Color: 9} + ANSIBrightGreen = ANSIColor{Color: 10} + ANSIBrightYellow = ANSIColor{Color: 11} + ANSIBrightBlue = ANSIColor{Color: 12} + ANSIBrightMagenta = ANSIColor{Color: 13} + ANSIBrightCyan = ANSIColor{Color: 14} + ANSIBrightWhite = ANSIColor{Color: 15} ) // RGB values of ANSI colors (0-255). diff --git a/color.go b/color.go index 0d11f43..10b48d5 100644 --- a/color.go +++ b/color.go @@ -14,17 +14,30 @@ var ( ErrInvalidColor = errors.New("invalid color") ) +const ( + // Foreground sequence code. + ForegroudSeq = "38" + // Background sequence code. + BackgroundSeq = "48" +) + // Foreground and Background sequence codes const ( - Foreground = "38" - Background = "48" + // Foreground sequence code. + // + // Deprecated: use ForegroudSeq + Foreground = ForegroudSeq + // Background sequence code. + // + // Deprecated: use BackgroundSeq + Background = BackgroundSeq ) // Color is an interface implemented by all colors that can be converted to an // ANSI sequence. type Color interface { // Sequence returns the ANSI Sequence for the color. - Sequence(bg bool) string + Sequence() string } // NoColor is a nop for terminals that don't support colors. @@ -35,10 +48,13 @@ func (c NoColor) String() string { } // ANSIColor is a color (0-15) as defined by the ANSI Standard. -type ANSIColor int +type ANSIColor struct { + Color int + bg bool +} func (c ANSIColor) String() string { - return ansiHex[c] + return ansiHex[c.Color] } // ANSI256Color is a color (16-255) as defined by the ANSI Standard. @@ -58,7 +74,7 @@ func ConvertToRGB(c Color) colorful.Color { case RGBColor: hex = string(v) case ANSIColor: - hex = ansiHex[v] + hex = ansiHex[v.Color] case ANSI256Color: hex = ansiHex[v] } @@ -68,18 +84,18 @@ func ConvertToRGB(c Color) colorful.Color { } // Sequence returns the ANSI Sequence for the color. -func (c NoColor) Sequence(bg bool) string { +func (c NoColor) Sequence() string { return "" } // Sequence returns the ANSI Sequence for the color. -func (c ANSIColor) Sequence(bg bool) string { - col := int(c) - bgMod := func(c int) int { - if bg { - return c + 10 +func (c ANSIColor) Sequence() string { + col := int(c.Color) + bgMod := func(col int) int { + if c.bg { + return col + 10 } - return c + return col } if col < 8 { @@ -89,26 +105,18 @@ func (c ANSIColor) Sequence(bg bool) string { } // Sequence returns the ANSI Sequence for the color. -func (c ANSI256Color) Sequence(bg bool) string { - prefix := Foreground - if bg { - prefix = Background - } - return fmt.Sprintf("%s;5;%d", prefix, c) +func (c ANSI256Color) Sequence() string { + return fmt.Sprintf("5;%d", c) } // Sequence returns the ANSI Sequence for the color. -func (c RGBColor) Sequence(bg bool) string { +func (c RGBColor) Sequence() string { f, err := colorful.Hex(string(c)) if err != nil { return "" } - prefix := Foreground - if bg { - prefix = Background - } - return fmt.Sprintf("%s;2;%d;%d;%d", prefix, uint8(f.R*255), uint8(f.G*255), uint8(f.B*255)) + return fmt.Sprintf("2;%d;%d;%d", uint8(f.R*255), uint8(f.G*255), uint8(f.B*255)) } func xTermColor(s string) (RGBColor, error) { @@ -155,7 +163,7 @@ func ansi256ToANSIColor(c ANSI256Color) ANSIColor { } } - return ANSIColor(r) + return ANSIColor{Color: r} } func hexToANSI256Color(c colorful.Color) ANSI256Color { diff --git a/profile.go b/profile.go index fa128e2..74595b4 100644 --- a/profile.go +++ b/profile.go @@ -81,7 +81,7 @@ func (p Profile) Color(s string) Color { } if i < 16 { - c = ANSIColor(i) + c = ANSIColor{Color: i} } else { c = ANSI256Color(i) } diff --git a/style.go b/style.go index 83b0b4d..e0138d3 100644 --- a/style.go +++ b/style.go @@ -59,7 +59,15 @@ func (t Style) Styled(s string) string { // Foreground sets a foreground color. func (t Style) Foreground(c Color) Style { if c != nil { - t.styles = append(t.styles, c.Sequence(false)) + if ac, ok := c.(ANSIColor); ok { + // ANSIColor(s) are their own sequences. + ac.bg = false + c = ac + } else if _, ok := c.(NoColor); !ok { + // NoColor can't have any sequences + t.styles = append(t.styles, ForegroudSeq) + } + t.styles = append(t.styles, c.Sequence()) } return t } @@ -67,7 +75,15 @@ func (t Style) Foreground(c Color) Style { // Background sets a background color. func (t Style) Background(c Color) Style { if c != nil { - t.styles = append(t.styles, c.Sequence(true)) + if ac, ok := c.(ANSIColor); ok { + // ANSIColor(s) are their own sequences. + ac.bg = true + c = ac + } else if _, ok := c.(NoColor); !ok { + // NoColor can't have any sequences + t.styles = append(t.styles, BackgroundSeq) + } + t.styles = append(t.styles, c.Sequence()) } return t } diff --git a/termenv_other.go b/termenv_other.go index 93a43b6..d52cd57 100644 --- a/termenv_other.go +++ b/termenv_other.go @@ -13,12 +13,12 @@ func (o Output) ColorProfile() Profile { func (o Output) foregroundColor() Color { // default gray - return ANSIColor(7) + return ANSIColor{Color: 7} } func (o Output) backgroundColor() Color { // default black - return ANSIColor(0) + return ANSIColor{Color: 0} } // EnableVirtualTerminalProcessing enables virtual terminal processing on diff --git a/termenv_test.go b/termenv_test.go index 87f22ad..1312265 100644 --- a/termenv_test.go +++ b/termenv_test.go @@ -17,14 +17,14 @@ func TestLegacyTermEnv(t *testing.T) { } fg := ForegroundColor() - fgseq := fg.Sequence(false) + fgseq := fg.Sequence() fgexp := "97" if fgseq != fgexp && fgseq != "" { t.Errorf("Expected %s, got %s", fgexp, fgseq) } bg := BackgroundColor() - bgseq := bg.Sequence(true) + bgseq := bg.Sequence() bgexp := "48;2;0;0;0" if bgseq != bgexp && bgseq != "" { t.Errorf("Expected %s, got %s", bgexp, bgseq) @@ -40,14 +40,14 @@ func TestTermEnv(t *testing.T) { } fg := o.ForegroundColor() - fgseq := fg.Sequence(false) + fgseq := fg.Sequence() fgexp := "97" if fgseq != fgexp && fgseq != "" { t.Errorf("Expected %s, got %s", fgexp, fgseq) } bg := o.BackgroundColor() - bgseq := bg.Sequence(true) + bgseq := bg.Sequence() bgexp := "48;2;0;0;0" if bgseq != bgexp && bgseq != "" { t.Errorf("Expected %s, got %s", bgexp, bgseq) @@ -115,16 +115,16 @@ func TestColorConversion(t *testing.T) { func TestFromColor(t *testing.T) { // color.Color interface c := TrueColor.FromColor(color.RGBA{255, 128, 0, 255}) - exp := "38;2;255;128;0" - if c.Sequence(false) != exp { - t.Errorf("Expected %s, got %s", exp, c.Sequence(false)) + exp := "2;255;128;0" + if c.Sequence() != exp { + t.Errorf("Expected %s, got %s", exp, c.Sequence()) } } func TestAscii(t *testing.T) { c := Ascii.Color("#abcdef") - if c.Sequence(false) != "" { - t.Errorf("Expected empty sequence, got %s", c.Sequence(false)) + if c.Sequence() != "" { + t.Errorf("Expected empty sequence, got %s", c.Sequence()) } } @@ -133,8 +133,8 @@ func TestANSIProfile(t *testing.T) { c := p.Color("#e88388") exp := "91" - if c.Sequence(false) != exp { - t.Errorf("Expected %s, got %s", exp, c.Sequence(false)) + if c.Sequence() != exp { + t.Errorf("Expected %s, got %s", exp, c.Sequence()) } if _, ok := c.(ANSIColor); !ok { t.Errorf("Expected type termenv.ANSIColor, got %T", c) @@ -142,8 +142,8 @@ func TestANSIProfile(t *testing.T) { c = p.Color("82") exp = "92" - if c.Sequence(false) != exp { - t.Errorf("Expected %s, got %s", exp, c.Sequence(false)) + if c.Sequence() != exp { + t.Errorf("Expected %s, got %s", exp, c.Sequence()) } if _, ok := c.(ANSIColor); !ok { t.Errorf("Expected type termenv.ANSIColor, got %T", c) @@ -151,8 +151,8 @@ func TestANSIProfile(t *testing.T) { c = p.Color("2") exp = "32" - if c.Sequence(false) != exp { - t.Errorf("Expected %s, got %s", exp, c.Sequence(false)) + if c.Sequence() != exp { + t.Errorf("Expected %s, got %s", exp, c.Sequence()) } if _, ok := c.(ANSIColor); !ok { t.Errorf("Expected type termenv.ANSIColor, got %T", c) @@ -163,18 +163,18 @@ func TestANSI256Profile(t *testing.T) { p := ANSI256 c := p.Color("#abcdef") - exp := "38;5;153" - if c.Sequence(false) != exp { - t.Errorf("Expected %s, got %s", exp, c.Sequence(false)) + exp := "5;153" + if c.Sequence() != exp { + t.Errorf("Expected %s, got %s", exp, c.Sequence()) } if _, ok := c.(ANSI256Color); !ok { t.Errorf("Expected type termenv.ANSI256Color, got %T", c) } c = p.Color("139") - exp = "38;5;139" - if c.Sequence(false) != exp { - t.Errorf("Expected %s, got %s", exp, c.Sequence(false)) + exp = "5;139" + if c.Sequence() != exp { + t.Errorf("Expected %s, got %s", exp, c.Sequence()) } if _, ok := c.(ANSI256Color); !ok { t.Errorf("Expected type termenv.ANSI256Color, got %T", c) @@ -182,8 +182,8 @@ func TestANSI256Profile(t *testing.T) { c = p.Color("2") exp = "32" - if c.Sequence(false) != exp { - t.Errorf("Expected %s, got %s", exp, c.Sequence(false)) + if c.Sequence() != exp { + t.Errorf("Expected %s, got %s", exp, c.Sequence()) } if _, ok := c.(ANSIColor); !ok { t.Errorf("Expected type termenv.ANSIColor, got %T", c) @@ -194,18 +194,18 @@ func TestTrueColorProfile(t *testing.T) { p := TrueColor c := p.Color("#abcdef") - exp := "38;2;171;205;239" - if c.Sequence(false) != exp { - t.Errorf("Expected %s, got %s", exp, c.Sequence(false)) + exp := "2;171;205;239" + if c.Sequence() != exp { + t.Errorf("Expected %s, got %s", exp, c.Sequence()) } if _, ok := c.(RGBColor); !ok { t.Errorf("Expected type termenv.HexColor, got %T", c) } c = p.Color("139") - exp = "38;5;139" - if c.Sequence(false) != exp { - t.Errorf("Expected %s, got %s", exp, c.Sequence(false)) + exp = "5;139" + if c.Sequence() != exp { + t.Errorf("Expected %s, got %s", exp, c.Sequence()) } if _, ok := c.(ANSI256Color); !ok { t.Errorf("Expected type termenv.ANSI256Color, got %T", c) @@ -213,8 +213,8 @@ func TestTrueColorProfile(t *testing.T) { c = p.Color("2") exp = "32" - if c.Sequence(false) != exp { - t.Errorf("Expected %s, got %s", exp, c.Sequence(false)) + if c.Sequence() != exp { + t.Errorf("Expected %s, got %s", exp, c.Sequence()) } if _, ok := c.(ANSIColor); !ok { t.Errorf("Expected type termenv.ANSIColor, got %T", c) @@ -364,13 +364,13 @@ func TestPseudoTerm(t *testing.T) { } fg := o.ForegroundColor() - fgseq := fg.Sequence(false) + fgseq := fg.Sequence() if fgseq != "" { t.Errorf("Expected empty response, got %s", fgseq) } bg := o.BackgroundColor() - bgseq := bg.Sequence(true) + bgseq := bg.Sequence() if bgseq != "" { t.Errorf("Expected empty response, got %s", bgseq) } diff --git a/termenv_unix.go b/termenv_unix.go index 11746d2..d15d9df 100644 --- a/termenv_unix.go +++ b/termenv_unix.go @@ -79,12 +79,12 @@ func (o Output) foregroundColor() Color { c := strings.Split(colorFGBG, ";") i, err := strconv.Atoi(c[0]) if err == nil { - return ANSIColor(i) + return ANSIColor{Color: i} } } // default gray - return ANSIColor(7) + return ANSIColor{Color: 7} } func (o Output) backgroundColor() Color { @@ -101,12 +101,12 @@ func (o Output) backgroundColor() Color { c := strings.Split(colorFGBG, ";") i, err := strconv.Atoi(c[len(c)-1]) if err == nil { - return ANSIColor(i) + return ANSIColor{Color: i} } } // default black - return ANSIColor(0) + return ANSIColor{Color: 0} } func (o *Output) waitForData(timeout time.Duration) error { diff --git a/termenv_windows.go b/termenv_windows.go index 1d9c618..e3e6b4b 100644 --- a/termenv_windows.go +++ b/termenv_windows.go @@ -45,12 +45,12 @@ func (o *Output) ColorProfile() Profile { func (o Output) foregroundColor() Color { // default gray - return ANSIColor(7) + return ANSIColor{Color: 7} } func (o Output) backgroundColor() Color { // default black - return ANSIColor(0) + return ANSIColor{Color: 0} } // EnableWindowsANSIConsole enables virtual terminal processing on Windows