Repository: pterm/pterm (go)

Issue:
Proposal: Add RGBStyle similar to Style but for RGB colors
The RGBStyle should have a foreground color, an optional background color and styles like italic, bold and so on.

Pull request:
Added RGBStyle
### Description
Describe your work here.

### Scope
> What is affected by this pull request?

- [ ] Bug Fix
- [x] New Feature
- [x] Documentation
- [ ] Other

### Related Issue
Fixes #491 


### To-Do Checklist
- [x] I tested my changes
- [x] I have commented every method that I created/changed
- [x] I updated the examples to fit with my changes
- [x] I have added tests for my newly created methods


Patch:
diff --git a/_examples/coloring/fade-colors-rgb-style/main.go b/_examples/coloring/fade-colors-rgb-style/main.go
new file mode 100644
index 00000000..86a61a8c
--- /dev/null
+++ b/_examples/coloring/fade-colors-rgb-style/main.go
@@ -0,0 +1,67 @@
+package main
+
+import (
+	"strings"
+
+	"github.com/pterm/pterm"
+)
+
+func main() {
+	white := pterm.NewRGB(255, 255, 255) // This RGB value is used as the gradients start point.
+	grey := pterm.NewRGB(128, 128, 128)  // This RGB value is used as the gradients start point.
+	black := pterm.NewRGB(0, 0, 0)       // This RGB value is used as the gradients start point.
+	red := pterm.NewRGB(255, 0, 0)       // This RGB value is used as the gradients start point.
+	purple := pterm.NewRGB(255, 0, 255)  // This RGB value is used as the gradients start point.
+	green := pterm.NewRGB(0, 255, 0)     // This RGB value is used as the gradients start point.
+
+	str := "RGB colors only work in Terminals which support TrueColor."
+	strs := strings.Split(str, "")
+	var fadeInfo string // String which will be used to print.
+	for i := 0; i < len(str); i++ {
+		// Append faded letter to info string.
+		fadeInfo += pterm.NewRGBStyle(white.Fade(0, float32(len(str)), float32(i), purple), grey.Fade(0, float32(len(str)), float32(i), black)).Sprint(strs[i])
+	}
+
+	pterm.Info.Println(fadeInfo)
+
+	str = "The background and foreground colors can be customized individually."
+	strs = strings.Split(str, "")
+	var fade2 string // String which will be used to print info.
+	for i := 0; i < len(str); i++ {
+		// Append faded letter to info string.
+		fade2 += pterm.NewRGBStyle(black, purple.Fade(0, float32(len(str)), float32(i), red)).Sprint(strs[i])
+	}
+
+	pterm.Println(fade2)
+
+	str = "Styles can also be applied. For example: Bold or Italic."
+	strs = strings.Split(str, "")
+	var fade3 string // String which will be used to print.
+
+	bold := 0
+	boldStr := strings.Split("Bold", "")
+	italic := 0
+	italicStr := strings.Split("Italic", "")
+
+	for i := 0; i < len(str); i++ {
+		// Append faded letter to info string.
+		s := pterm.NewRGBStyle(white.Fade(0, float32(len(str)), float32(i), green), red.Fade(0, float32(len(str)), float32(i), black))
+
+		// if the next letters are "Bold", then add the style "Bold".
+		// else if the next letters are "Italic", then add the style "Italic".
+		if bold < len(boldStr) && i+len(boldStr) <= len(strs) {
+			if strings.Join(strs[i:i+len(boldStr)-bold], "") == strings.Join(boldStr[bold:], "") {
+				s = s.AddOptions(pterm.Bold)
+				bold++
+			}
+		} else if italic < len(italicStr) && i+len(italicStr)-italic < len(strs) {
+			if strings.Join(strs[i:i+len(italicStr)-italic], "") == strings.Join(italicStr[italic:], "") {
+				s = s.AddOptions(pterm.Italic)
+				italic++
+			}
+		}
+		fade3 += s.Sprint(strs[i])
+	}
+
+	pterm.Println(fade3)
+}
diff --git a/_examples/coloring/print-color-rgb-style/main.go b/_examples/coloring/print-color-rgb-style/main.go
new file mode 100644
index 00000000..053c9e88
--- /dev/null
+++ b/_examples/coloring/print-color-rgb-style/main.go
@@ -0,0 +1,19 @@
+package main
+
+import (
+	"github.com/pterm/pterm"
+)
+
+func main() {
+	foregroundRGB := pterm.RGB{R: 187, G: 80, B: 0}
+	backgroundRGB := pterm.RGB{R: 0, G: 50, B: 123}
+
+	// Print string with a custom foreground and background RGB color.
+	pterm.NewRGBStyle(foregroundRGB, backgroundRGB).Println("This text is not styled.")
+
+	// Print string with a custom foreground and background RGB color and style bold.
+	pterm.NewRGBStyle(foregroundRGB, backgroundRGB).AddOptions(pterm.Bold).Println("This text is bold.")
+
+	// Print string with a custom foreground and background RGB color and style italic.
+	pterm.NewRGBStyle(foregroundRGB, backgroundRGB).AddOptions(pterm.Italic).Println("This text is italic.")
+}
diff --git a/rgb.go b/rgb.go
index 23b2fa48..a35dd1a0 100644
--- a/rgb.go
+++ b/rgb.go
@@ -18,6 +18,133 @@ type RGB struct {
 	Background bool
 }
 
+type RGBStyle struct {
+	Options                []Color
+	Foreground, Background RGB
+
+	hasBg bool
+}
+
+// NewRGBStyle returns a new RGBStyle.
+// The foreground color is required, the background color is optional.
+// The colors will be set as is, ignoring the RGB.Background property.
+func NewRGBStyle(foreground RGB, background ...RGB) RGBStyle {
+	var s RGBStyle
+	s.Foreground = foreground
+	if len(background) > 0 {
+		s.Background = background[0]
+		s.hasBg = true
+	}
+	return s
+}
+
+// AddOptions adds options to the RGBStyle.
+func (p RGBStyle) AddOptions(opts ...Color) RGBStyle {
+	p.Options = append(p.Options, opts...)
+	return p
+}
+
+// Print formats using the default formats for its operands and writes to standard output.
+// Spaces are added between operands when neither is a string.
+// It returns the number of bytes written and any write error encountered.
+func (p RGBStyle) Print(a ...interface{}) *TextPrinter {
+	Print(p.Sprint(a...))
+	tp := TextPrinter(p)
+	return &tp
+}
+
+// Println formats using the default formats for its operands and writes to standard output.
+// Spaces are always added between operands and a newline is appended.
+// It returns the number of bytes written and any write error encountered.
+func (p RGBStyle) Println(a ...interface{}) *TextPrinter {
+	Println(p.Sprint(a...))
+	tp := TextPrinter(p)
+	return &tp
+}
+
+// Printf formats according to a format specifier and writes to standard output.
+// It returns the number of bytes written and any write error encountered.
+func (p RGBStyle) Printf(format string, a ...interface{}) *TextPrinter {
+	Printf(format, p.Sprint(a...))
+	tp := TextPrinter(p)
+	return &tp
+}
+
+// Printfln formats according to a format specifier and writes to standard output.
+// Spaces are always added between operands and a newline is appended.
+// It returns the number of bytes written and any write error encountered.
+func (p RGBStyle) Printfln(format string, a ...interface{}) *TextPrinter {
+	Printf(format, p.Sprint(a...))
+	tp := TextPrinter(p)
+	return &tp
+}
+
+// PrintOnError prints every error which is not nil.
+// If every error is nil, nothing will be printed.
+// This can be used for simple error checking.
+func (p RGBStyle) PrintOnError(a ...interface{}) *TextPrinter {
+	for _, arg := range a {
+		if err, ok := arg.(error); ok {
+			if err != nil {
+				p.Println(err)
+			}
+		}
+	}
+
+	tp := TextPrinter(p)
+	return &tp
+}
+
+// PrintOnErrorf wraps every error which is not nil and prints it.
+// If every error is nil, nothing will be printed.
+// This can be used for simple error checking.
+func (p RGBStyle) PrintOnErrorf(format string, a ...interface{}) *TextPrinter {
+	for _, arg := range a {
+		if err, ok := arg.(error); ok {
+			if err != nil {
+				p.Println(fmt.Errorf(format, err))
+			}
+		}
+	}
+
+	tp := TextPrinter(p)
+	return &tp
+}
+
+// Sprint formats using the default formats for its operands and returns the resulting string.
+// Spaces are added between operands when neither is a string.
+func (p RGBStyle) Sprint(a ...interface{}) string {
+	var rgbStyle *color.RGBStyle
+	if !p.hasBg {
+		rgbStyle = color.NewRGBStyle(color.RGB(p.Foreground.R, p.Foreground.G, p.Foreground.B))
+	} else {
+		rgbStyle = color.NewRGBStyle(color.RGB(p.Foreground.R, p.Foreground.G, p.Foreground.B), color.RGB(p.Background.R, p.Background.G, p.Background.B))
+	}
+	if len(p.Options) > 0 {
+		for _, opt := range p.Options {
+			rgbStyle.AddOpts(color.Color(opt))
+		}
+	}
+	return rgbStyle.Sprint(a...)
+}
+
+// Sprintln formats using the default formats for its operands and returns the resulting string.
+// Spaces are always added between operands and a newline is appended.
+func (p RGBStyle) Sprintln(a ...interface{}) string {
+	return p.Sprint(a...) + "\n"
+}
+
+// Sprintf formats according to a format specifier and returns the resulting string.
+func (p RGBStyle) Sprintf(format string, a ...interface{}) string {
+	return fmt.Sprintf(format, p.Sprint(a...))
+}
+
+// Sprintfln formats according to a format specifier and returns the resulting string.
+// Spaces are always added between operands and a newline is appended.
+func (p RGBStyle) Sprintfln(format string, a ...interface{}) string {
+	return fmt.Sprintf(format, p.Sprint(a...)) + "\n"
+}
+
 // GetValues returns the RGB values separately.
 func (p RGB) GetValues() (r, g, b uint8) {
 	return p.R, p.G, p.B
@@ -160,3 +287,11 @@ func (p RGB) PrintOnErrorf(format string, a ...interface{}) *TextPrinter {
 	tp := TextPrinter(p)
 	return &tp
 }
+
+func (p RGB) ToRGBStyle() RGBStyle {
+	if p.Background {
+		return RGBStyle{Background: p}
+	}
+
+	return RGBStyle{Foreground: p}
+}


Test patch:
diff --git a/rgb_test.go b/rgb_test.go
index 28619056..edea23f9 100644
--- a/rgb_test.go
+++ b/rgb_test.go
@@ -305,3 +305,71 @@ func TestRGB_PrintIfError_WithoutErrorf(t *testing.T) {
 		})
 	}
 }
+
+func TestRGBStyle_PrintOnError(t *testing.T) {
+	RGBs := []pterm.RGBStyle{{Foreground: pterm.RGB{R: 10, G: 10, B: 10}}, {Foreground: pterm.RGB{R: 0, G: 0, B: 255}, Background: pterm.RGB{R: 255, G: 0, B: 255}, Options: []pterm.Color{pterm.Bold}},
+		{Foreground: pterm.RGB{R: 0, G: 0, B: 255}, Background: pterm.RGB{R: 255, G: 0, B: 255}, Options: []pterm.Color{pterm.Bold, pterm.Italic}}}
+	for _, rgb := range RGBs {
+		t.Run("PrintOnError", func(t *testing.T) {
+			result := captureStdout(func(w io.Writer) {
+				rgb.PrintOnError(errors.New("hello world"))
+			})
+			testza.AssertContains(t, result, "hello world")
+		})
+	}
+}
+
+func TestRGBStyle_PrintIfError_WithoutError(t *testing.T) {
+	RGBs := []pterm.RGBStyle{{Foreground: pterm.RGB{R: 10, G: 10, B: 10}}, {Foreground: pterm.RGB{R: 0, G: 0, B: 255}, Background: pterm.RGB{R: 255, G: 0, B: 255}, Options: []pterm.Color{pterm.Bold}},
+		{Foreground: pterm.RGB{R: 0, G: 0, B: 255}, Background: pterm.RGB{R: 255, G: 0, B: 255}, Options: []pterm.Color{pterm.Bold, pterm.Italic}}}
+	for _, rgb := range RGBs {
+		t.Run("PrintIfError_WithoutError", func(t *testing.T) {
+			result := captureStdout(func(w io.Writer) {
+				rgb.PrintOnError(nil)
+			})
+			testza.AssertZero(t, result)
+		})
+	}
+}
+
+func TestRGBStyle_PrintOnErrorf(t *testing.T) {
+	RGBs := []pterm.RGBStyle{{Foreground: pterm.RGB{R: 10, G: 10, B: 10}}, {Foreground: pterm.RGB{R: 0, G: 0, B: 255}, Background: pterm.RGB{R: 255, G: 0, B: 255}, Options: []pterm.Color{pterm.Bold}},
+		{Foreground: pterm.RGB{R: 0, G: 0, B: 255}, Background: pterm.RGB{R: 255, G: 0, B: 255}, Options: []pterm.Color{pterm.Bold, pterm.Italic}}}
+	for _, rgb := range RGBs {
+		t.Run("PrintOnErrorf", func(t *testing.T) {
+			result := captureStdout(func(w io.Writer) {
+				rgb.PrintOnErrorf("wrapping error : %w", errors.New("hello world"))
+			})
+			testza.AssertContains(t, result, "hello world")
+		})
+	}
+}
+
+func TestRGBStyle_PrintIfError_WithoutErrorf(t *testing.T) {
+	RGBs := []pterm.RGBStyle{{Foreground: pterm.RGB{R: 10, G: 10, B: 10}}, {Foreground: pterm.RGB{R: 0, G: 0, B: 255}, Background: pterm.RGB{R: 255, G: 0, B: 255}, Options: []pterm.Color{pterm.Bold}},
+		{Foreground: pterm.RGB{R: 0, G: 0, B: 255}, Background: pterm.RGB{R: 255, G: 0, B: 255}, Options: []pterm.Color{pterm.Bold, pterm.Italic}}}
+	for _, rgb := range RGBs {
+		t.Run("PrintIfError_WithoutErrorf", func(t *testing.T) {
+			result := captureStdout(func(w io.Writer) {
+				rgb.PrintOnErrorf("", nil)
+			})
+			testza.AssertZero(t, result)
+		})
+	}
+}
+
+func TestRGBStyle_NewRGBStyle(t *testing.T) {
+	RGBs := []pterm.RGBStyle{{Foreground: pterm.RGB{R: 10, G: 10, B: 10}}, {Foreground: pterm.RGB{R: 0, G: 0, B: 255}, Background: pterm.RGB{R: 255, G: 0, B: 255}, Options: []pterm.Color{pterm.Bold}},
+		{Foreground: pterm.RGB{R: 0, G: 0, B: 255}, Background: pterm.RGB{R: 255, G: 0, B: 255}, Options: []pterm.Color{pterm.Bold, pterm.Italic}}}
+	for _, rgb := range RGBs {
+		t.Run("NewRGBStyle", func(t *testing.T) {
+			result := pterm.NewRGBStyle(rgb.Foreground, rgb.Background)
+			if len(rgb.Options) > 0 {
+				result = result.AddOptions(rgb.Options...)
+			}
+			testza.AssertEqual(t, rgb.Foreground, result.Foreground)
+			testza.AssertEqual(t, rgb.Background, result.Background)
+			testza.AssertEqual(t, rgb.Options, result.Options)
+		})
+	}
+}