diff --git a/clifmt.go b/clifmt.go new file mode 100644 index 0000000..2054f17 --- /dev/null +++ b/clifmt.go @@ -0,0 +1,42 @@ +package clifmt + +import ( + "fmt" + "git.xdrm.io/go/clifmt/internal/color" + colorTransform "git.xdrm.io/go/clifmt/internal/transform/color" + mdTransform "git.xdrm.io/go/clifmt/internal/transform/markdown" +) + +var theme = color.DefaultTheme() + +// Sprintf returns a terminal-colorized output following the coloring format +func Sprintf(format string, a ...interface{}) (string, error) { + // 1. Pre-process format with 'fmt' + formatted := fmt.Sprintf(format, a...) + + // 2. Colorize + colorized, err := colorTransform.Transform(formatted, theme) + if err != nil { + return "", err + } + + // 3. Markdown format + markdown, err := mdTransform.Transform(colorized) + if err != nil { + return "", err + } + + // 3. return final output + return markdown, nil +} + +// Printf prints a terminal-colorized output following the coloring format +func Printf(format string, a ...interface{}) error { + s, err := Sprintf(format, a...) + if err != nil { + return err + } + + fmt.Print(s) + return nil +} diff --git a/clifmt_test.go b/clifmt_test.go new file mode 100644 index 0000000..d528590 --- /dev/null +++ b/clifmt_test.go @@ -0,0 +1,162 @@ +package clifmt + +import ( + "strings" + "testing" +) + +func TestColoring(t *testing.T) { + + tests := []struct { + Input string + Output string + }{ + // foreground + background + { + "start ${some text input}(#ff0000:#00ff00) end\n", + "start \x1b[38;2;255;0;0;48;2;0;255;0msome text input\x1b[0m end\n", + }, { + "start ${some text input}(#f00:#0f0) end\n", + "start \x1b[38;2;255;0;0;48;2;0;255;0msome text input\x1b[0m end\n", + }, { + "start ${some text input}(red:green) end\n", + "start \x1b[38;2;255;0;0;48;2;0;255;0msome text input\x1b[0m end\n", + }, + + // mixed notations + { + "start ${some text input}(red:#00ff00) end\n", + "start \x1b[38;2;255;0;0;48;2;0;255;0msome text input\x1b[0m end\n", + }, { + "start ${some text input}(red:#0f0) end\n", + "start \x1b[38;2;255;0;0;48;2;0;255;0msome text input\x1b[0m end\n", + }, { + "start ${some text input}(#ff0000:green) end\n", + "start \x1b[38;2;255;0;0;48;2;0;255;0msome text input\x1b[0m end\n", + }, { + "start ${some text input}(#f00:green) end\n", + "start \x1b[38;2;255;0;0;48;2;0;255;0msome text input\x1b[0m end\n", + }, + + // foreground only + { + "start ${some text input}(red) end\n", + "start \x1b[38;2;255;0;0msome text input\x1b[0m end\n", + }, { + "start ${some text input}(#ff0000) end\n", + "start \x1b[38;2;255;0;0msome text input\x1b[0m end\n", + }, { + "start ${some text input}(#f00) end\n", + "start \x1b[38;2;255;0;0msome text input\x1b[0m end\n", + }, + + // background only + { + "start ${some text input}(:blue) end\n", + "start \x1b[48;2;0;0;255msome text input\x1b[0m end\n", + }, { + "start ${some text input}(:#0000ff) end\n", + "start \x1b[48;2;0;0;255msome text input\x1b[0m end\n", + }, { + "start ${some text input}(:#00f) end\n", + "start \x1b[48;2;0;0;255msome text input\x1b[0m end\n", + }, + + // multi matches + { + "start ${text1}(red) separation ${text2}(#0f0) end\n", + "start \x1b[38;2;255;0;0mtext1\x1b[0m separation \x1b[38;2;0;255;0mtext2\x1b[0m end\n", + }, { + "start ${text1}(:red) separation ${text2}(:#0f0) end\n", + "start \x1b[48;2;255;0;0mtext1\x1b[0m separation \x1b[48;2;0;255;0mtext2\x1b[0m end\n", + }} + + for i, test := range tests { + output, err := Sprintf(test.Input) + if err != nil { + t.Errorf("[%d] unexpected error <%v>", i, err) + break + } + + if output != test.Output { + t.Errorf("[%d] expected '%s', got '%s'", i, test.Output, output) + } + + } +} + +func TestMarkdown(t *testing.T) { + + tests := []struct { + Input string + Output string + }{ + // each notation + { + "start **bold text** end\n", + "start \x1b[1mbold text\x1b[22m end\n", + }, { + "start *italic text* end\n", + "start \x1b[3mitalic text\x1b[23m end\n", + }, { + "start _underlined text_ end\n", + "start \x1b[4munderlined text\x1b[24m end\n", + }, + + // mixed notations + { + "start ***bold italic*** end\n", + "start \x1b[3m\x1b[1mbold italic\x1b[23m\x1b[22m end\n", + }, { + "start **_bold underline_** end\n", + "start \x1b[1m\x1b[4mbold underline\x1b[24m\x1b[22m end\n", + }, { + "start _**bold underline**_ end\n", + "start \x1b[4m\x1b[1mbold underline\x1b[22m\x1b[24m end\n", + }, { + "start *_italic underline_* end\n", + "start \x1b[3m\x1b[4mitalic underline\x1b[24m\x1b[23m end\n", + }, { + "start _*italic underline*_ end\n", + "start \x1b[4m\x1b[3mitalic underline\x1b[23m\x1b[24m end\n", + }, { + "start _***bold italic underline***_ end\n", + "start \x1b[4m\x1b[3m\x1b[1mbold italic underline\x1b[23m\x1b[22m\x1b[24m end\n", + }, { + "start **_*bold italic underline*_** end\n", + "start \x1b[1m\x1b[4m\x1b[3mbold italic underline\x1b[23m\x1b[24m\x1b[22m end\n", + }, { + "start *_**bold italic underline**_* end\n", + "start \x1b[3m\x1b[4m\x1b[1mbold italic underline\x1b[22m\x1b[24m\x1b[23m end\n", + }, { + "start _***bold italic underline***_ end\n", + "start \x1b[4m\x1b[3m\x1b[1mbold italic underline\x1b[23m\x1b[22m\x1b[24m end\n", + }, + + // mixed notations not matching + { + "start ***bold** italic* end\n", + "start \x1b[3m\x1b[1mbold\x1b[22m italic\x1b[23m end\n", + }, { + "start **_bold** underline_ end\n", + "start \x1b[1m\x1b[4mbold\x1b[22m underline\x1b[24m end\n", + }, { + "start _**bold_ underline** end\n", + "start \x1b[4m\x1b[1mbold\x1b[24m underline\x1b[22m end\n", + }, + } + + for i, test := range tests { + output, err := Sprintf(test.Input) + if err != nil { + t.Errorf("[%d] unexpected error <%v>", i, err) + break + } + + if output != test.Output { + t.Errorf("[%d] expected '%s'\n", i, strings.Replace(strings.Replace(test.Output, "\n", "\\n", -1), "\x1b", "\\e", -1)) + t.Errorf("[%d] got '%s'\n", i, strings.Replace(strings.Replace(output, "\n", "\\n", -1), "\x1b", "\\e", -1)) + } + + } +} diff --git a/colors.go b/colors.go deleted file mode 100644 index 9a9e914..0000000 --- a/colors.go +++ /dev/null @@ -1,99 +0,0 @@ -package clifmt - -import ( - "fmt" - "io" - "strconv" -) - -type terminalColor uint32 - -// Theme is used to associate color names to integer color values ; -// the color names in the theme are available in the colorizing format -type Theme map[string]terminalColor - -var theme Theme = make(map[string]terminalColor) - -func init() { - theme["black"] = 0x000000 - theme["white"] = 0xffffff - theme["red"] = 0xff0000 - theme["green"] = 0x00ff00 - theme["blue"] = 0x0000ff - theme["yellow"] = 0xffff00 - theme["orange"] = 0xff8c00 - theme["purple"] = 0x800080 - theme["navy"] = 0x000080 - theme["aqua"] = 0x00ffff - theme["gray"] = 0x808080 - theme["silver"] = 0xc0c0c0 - theme["fuchsia"] = 0xff00ff - theme["olive"] = 0x808000 - theme["teal"] = 0x008080 - theme["brown"] = 0x800000 -} - -// fromName returns the integer value of a color name -// from the built-in color map ; it is case insensitive -func fromName(s string) (terminalColor, error) { - value, ok := theme[s] - if !ok { - return 0, fmt.Errorf("unknown color name '%s'", s) - } - return value, nil -} - -// fromHex returns the integer value associated with -// an hexadecimal string (full-sized or short version) -// the format is 'abc' or 'abcdef' -func fromHex(s string) (terminalColor, error) { - if len(s) != 3 && len(s) != 6 { - return 0, fmt.Errorf("expect a size of 3 or 6 (remove the '#' prefix)") - } - - // short version - input := s - if len(s) == 3 { - input = fmt.Sprintf("%c%c%c%c%c%c", s[0], s[0], s[1], s[1], s[2], s[2]) - } - - n, err := strconv.ParseUint(input, 16, 32) - if err != nil { - return 0, err - } - - return terminalColor(n), nil -} - -// parseColor tries to parse a color string (can be a name or an hexa value) -func parseColor(s string) (terminalColor, error) { - - // (0) ... - if len(s) < 1 { - return 0, io.ErrUnexpectedEOF - } - - // (1) hexa - if s[0] == '#' { - return fromHex(s[1:]) - } - - // (2) name - return fromName(s) - -} - -// Red component of the color -func (c terminalColor) Red() uint8 { - return uint8((c >> 16) & 0xff) -} - -// Green component of the color -func (c terminalColor) Green() uint8 { - return uint8((c >> 8) & 0xff) -} - -// Blue component of the color -func (c terminalColor) Blue() uint8 { - return uint8(c & 0xff) -} diff --git a/internal/color/color.go b/internal/color/color.go new file mode 100644 index 0000000..4c10092 --- /dev/null +++ b/internal/color/color.go @@ -0,0 +1,75 @@ +package color + +import ( + "fmt" + "io" + "strconv" +) + +// T represents a color +type T uint32 + +// FromName returns the integer value of a color name +// from the built-in color map ; it is case insensitive +func FromName(t Theme, s string) (T, error) { + value, ok := t[s] + if !ok { + return 0, fmt.Errorf("unknown color name '%s'", s) + } + return value, nil +} + +// FromHex returns the integer value associated with +// an hexadecimal string (full-sized or short version) +// the format is 'abc' or 'abcdef' +func FromHex(s string) (T, error) { + if len(s) != 3 && len(s) != 6 { + return 0, fmt.Errorf("expect a size of 3 or 6 (without the '#' prefix)") + } + + // short version + input := s + if len(s) == 3 { + input = fmt.Sprintf("%c%c%c%c%c%c", s[0], s[0], s[1], s[1], s[2], s[2]) + } + + n, err := strconv.ParseUint(input, 16, 32) + if err != nil { + return 0, err + } + + return T(n), nil +} + +// Parse tries to parse a color string (can be a name or an hexa value) +func Parse(t Theme, s string) (T, error) { + + // (0) ... + if len(s) < 1 { + return 0, io.ErrUnexpectedEOF + } + + // (1) hexa + if s[0] == '#' { + return FromHex(s[1:]) + } + + // (2) name + return FromName(t, s) + +} + +// Red component of the color +func (c T) Red() uint8 { + return uint8((c >> 16) & 0xff) +} + +// Green component of the color +func (c T) Green() uint8 { + return uint8((c >> 8) & 0xff) +} + +// Blue component of the color +func (c T) Blue() uint8 { + return uint8(c & 0xff) +} diff --git a/internal/color/theme.go b/internal/color/theme.go new file mode 100644 index 0000000..933762d --- /dev/null +++ b/internal/color/theme.go @@ -0,0 +1,28 @@ +package color + +// Theme is used to associate color names to integer color values ; +// the color names in the theme are available in the colorizing format +type Theme map[string]T + +// Default loads sets the default theme associations +func DefaultTheme() Theme { + theme := make(Theme) + theme["black"] = 0x000000 + theme["white"] = 0xffffff + theme["red"] = 0xff0000 + theme["green"] = 0x00ff00 + theme["blue"] = 0x0000ff + theme["yellow"] = 0xffff00 + theme["orange"] = 0xff8c00 + theme["purple"] = 0x800080 + theme["navy"] = 0x000080 + theme["aqua"] = 0x00ffff + theme["gray"] = 0x808080 + theme["silver"] = 0xc0c0c0 + theme["fuchsia"] = 0xff00ff + theme["olive"] = 0x808000 + theme["teal"] = 0x008080 + theme["brown"] = 0x800000 + + return theme +} diff --git a/internal/transform/color/color.go b/internal/transform/color/color.go new file mode 100644 index 0000000..ad66299 --- /dev/null +++ b/internal/transform/color/color.go @@ -0,0 +1,92 @@ +package color + +import ( + "fmt" + "git.xdrm.io/go/clifmt/internal/color" + "regexp" +) + +// extractor helps extract features from the coloring format defined as follows : +// +// - [Color] -> [a-z] # named color +// - [Color] -> #[0-9a-f]{3} # hexa color (shortcode) +// - [Color] -> #[0-9a-f]{6} # hexa color (full-sized) +// - [Text] -> ANY +// - [Format] -> ${Text}(Color:Color) # foreground, background colors +// - [Format] -> ${Text}(Color) # foreground color only +// - [Format] -> ${Text}(:Color) # background color only +var extractor = regexp.MustCompile(`(?m)\${([^$]+)}\(((?:[a-z]+|#(?:[0-9a-f]{3}|[0-9a-f]{6})))?(?:\:((?:[a-z]+|#(?:[0-9a-f]{3}|[0-9a-f]{6}))))?\)`) + +// colorize returns the terminal-formatted @text colorized with the @fg and @bg colors +func colorize(t string, fg *color.T, bg *color.T) string { + // no coloring + if fg == nil && bg == nil { + return t + } + + // only foreground + if bg == nil { + return fmt.Sprintf("\x1b[38;2;%d;%d;%dm%s\x1b[0m", fg.Red(), fg.Green(), fg.Blue(), t) + } + // only background + if fg == nil { + return fmt.Sprintf("\x1b[48;2;%d;%d;%dm%s\x1b[0m", bg.Red(), bg.Green(), bg.Blue(), t) + } + + // both colors + return fmt.Sprintf("\x1b[38;2;%d;%d;%d;48;2;%d;%d;%dm%s\x1b[0m", fg.Red(), fg.Green(), fg.Blue(), bg.Red(), bg.Green(), bg.Blue(), t) +} + +// Transform the @input text colorized according to the @extractor format +func Transform(input string, theme color.Theme) (string, error) { + output := "" + cursor := int(0) + + // 1. Replace for each match + for _, match := range extractor.FindAllStringSubmatchIndex(input, -1) { + + // (1) add gap between input start OR previous match + output += input[cursor:match[0]] + cursor = match[1] + + // (2) extract features + var ( + text = "" + sFg = "" + sBg = "" + fg *color.T = nil + bg *color.T = nil + ) + + if match[3]-match[2] > 0 { + text = input[match[2]:match[3]] + } + if match[5]-match[4] > 0 { + sFg = input[match[4]:match[5]] + fgv, err := color.Parse(theme, sFg) + if err != nil { + return "", err + } + fg = &fgv + } + if match[7]-match[6] > 0 { + sBg = input[match[6]:match[7]] + bgv, err := color.Parse(theme, sBg) + if err != nil { + return "", err + } + bg = &bgv + } + + // (3) replace text with colorized text + output += colorize(text, fg, bg) + } + + // 2. Add end of input + if cursor < len(input)-1 { + output += input[cursor:] + } + + // 3. print final output + return output, nil +} diff --git a/internal/transform/markdown/bold.go b/internal/transform/markdown/bold.go new file mode 100644 index 0000000..1be4c1b --- /dev/null +++ b/internal/transform/markdown/bold.go @@ -0,0 +1,48 @@ +package markdown + +import ( + "fmt" + "regexp" + "strings" +) + +var boldRe = regexp.MustCompile(`(?m)\*\*((?:[^\*]+\*?)+)\*\*`) + +// boldify returns the terminal-formatted bold text @t +func boldify(t string) string { + return fmt.Sprintf("\x1b[1m%s\x1b[22m", strings.Replace(t, "\x1b[0m", "\x1b[0m\x1b[1m", -1)) +} + +// boldTransform the @input text using markdown-like syntax : +// - "normal **bold** normal" +func boldTransform(input string) (string, error) { + output := "" + cursor := int(0) + + // 1. Replace for each match + for _, match := range boldRe.FindAllStringSubmatchIndex(input, -1) { + + // (1) add gap between input start OR previous match + output += input[cursor:match[0]] + cursor = match[1] + + // (2) extract features + text := "" + + if match[3]-match[2] > 0 { + text = input[match[2]:match[3]] + } + + // (3) replace text with bold text + output += boldify(text) + } + + // 2. Add end of input + if cursor < len(input)-1 { + output += input[cursor:] + } + + // 3. print final output + return output, nil + +} diff --git a/internal/transform/markdown/italic.go b/internal/transform/markdown/italic.go new file mode 100644 index 0000000..f2bec0c --- /dev/null +++ b/internal/transform/markdown/italic.go @@ -0,0 +1,48 @@ +package markdown + +import ( + "fmt" + "regexp" + "strings" +) + +var italicRe = regexp.MustCompile(`(?m)\*([^\*]+)\*`) + +// italic returns the terminal-formatted italic text @t +func italic(t string) string { + return fmt.Sprintf("\x1b[3m%s\x1b[23m", strings.Replace(t, "\x1b[0m", "\x1b[0m\x1b[3m", -1)) +} + +// italicTransform the @input text using markdown-like syntax : +// - "normal *italic* normal" +func italicTransform(input string) (string, error) { + output := "" + cursor := int(0) + + // 1. Replace for each match + for _, match := range italicRe.FindAllStringSubmatchIndex(input, -1) { + + // (1) add gap between input start OR previous match + output += input[cursor:match[0]] + cursor = match[1] + + // (2) extract features + text := "" + + if match[3]-match[2] > 0 { + text = input[match[2]:match[3]] + } + + // (3) replace text with bold text + output += italic(text) + } + + // 2. Add end of input + if cursor < len(input)-1 { + output += input[cursor:] + } + + // 3. print final output + return output, nil + +} diff --git a/internal/transform/markdown/markdown.go b/internal/transform/markdown/markdown.go new file mode 100644 index 0000000..c583a17 --- /dev/null +++ b/internal/transform/markdown/markdown.go @@ -0,0 +1,24 @@ +package markdown + +func Transform(input string) (string, error) { + + // 1. bold + bold, err := boldTransform(input) + if err != nil { + return "", err + } + + // 2. italic + italic, err := italicTransform(bold) + if err != nil { + return "", err + } + + // 3. italic + underline, err := underlineTransform(italic) + if err != nil { + return "", err + } + + return underline, nil +} diff --git a/internal/transform/markdown/underline.go b/internal/transform/markdown/underline.go new file mode 100644 index 0000000..af2aafb --- /dev/null +++ b/internal/transform/markdown/underline.go @@ -0,0 +1,48 @@ +package markdown + +import ( + "fmt" + "regexp" + "strings" +) + +var underlineRe = regexp.MustCompile(`(?m)_([^_]+)_`) + +// underline returns the terminal-formatted underline text @t +func underline(t string) string { + return fmt.Sprintf("\x1b[4m%s\x1b[24m", strings.Replace(t, "\x1b[0m", "\x1b[0m\x1b[4m", -1)) +} + +// underlineTransform the @input text using markdown-like syntax : +// - "normal _underline_ normal" +func underlineTransform(input string) (string, error) { + output := "" + cursor := int(0) + + // 1. Replace for each match + for _, match := range underlineRe.FindAllStringSubmatchIndex(input, -1) { + + // (1) add gap between input start OR previous match + output += input[cursor:match[0]] + cursor = match[1] + + // (2) extract features + text := "" + + if match[3]-match[2] > 0 { + text = input[match[2]:match[3]] + } + + // (3) replace text with bold text + output += underline(text) + } + + // 2. Add end of input + if cursor < len(input)-1 { + output += input[cursor:] + } + + // 3. print final output + return output, nil + +} diff --git a/printer.go b/printer.go deleted file mode 100644 index bb83fa2..0000000 --- a/printer.go +++ /dev/null @@ -1,96 +0,0 @@ -package clifmt - -import ( - "fmt" - "regexp" -) - -// extractor helps extract features from the coloring format defined as follows : -// -// - [Color] -> [a-z] # named color -// - [Color] -> #[0-9a-f]{3} # hexa color (shortcode) -// - [Color] -> #[0-9a-f]{6} # hexa color (full-sized) -// - [Text] -> ANY -// - [Format] -> ${Text}(Color:Color) # foreground, background colors -// - [Format] -> ${Text}(Color) # foreground color only -// - [Format] -> ${Text}(:Color) # background color only -var extractor = regexp.MustCompile(`(?m)\${([^$]+)}\(((?:[a-z]+|#(?:[0-9a-f]{3}|[0-9a-f]{6})))?(?:\:((?:[a-z]+|#(?:[0-9a-f]{3}|[0-9a-f]{6}))))?\)`) - -// Sprintf returns a terminal-colorized output following the coloring format -func Sprintf(format string, a ...interface{}) (string, error) { - // 1. Pre-process format with 'fmt' - input := fmt.Sprintf(format, a...) - output := "" - cursor := int(0) - - // 2. extract color format matches - for _, match := range extractor.FindAllStringSubmatchIndex(input, -1) { - // (1) add gap between input start OR previous match - output += input[cursor:match[0]] - cursor = match[1] - - // (2) extract features - var err error - var ( - text = "" - sForeground = "" - sBackground = "" - foreground = terminalColor(0) - background = terminalColor(0) - ) - - if match[3]-match[2] > 0 { - text = input[match[2]:match[3]] - } - if match[5]-match[4] > 0 { - sForeground = input[match[4]:match[5]] - foreground, err = parseColor(sForeground) - if err != nil { - return "", err - } - } - if match[7]-match[6] > 0 { - sBackground = input[match[6]:match[7]] - background, err = parseColor(sBackground) - if err != nil { - return "", err - } - } - - // (3) replace text with colorized text - if len(sForeground) > 0 { - text = colorize(text, true, foreground) - } - if len(sBackground) > 0 { - text = colorize(text, false, background) - } - output += text - } - - // 3. Add end of input - if cursor < len(input)-1 { - output += input[cursor:] - } - - // 3. print final output - return output, nil -} - -// Printf prints a terminal-colorized output following the coloring format -func Printf(format string, a ...interface{}) error { - s, err := Sprintf(format, a...) - if err != nil { - return err - } - - fmt.Print(s) - return nil -} - -func colorize(text string, foregound bool, color terminalColor) string { - if foregound { - return fmt.Sprintf("\033[38;2;%d;%d;%dm%s\033[0m", color.Red(), color.Green(), color.Blue(), text) - } - - return fmt.Sprintf("\033[48;2;%d;%d;%dm%s\033[0m", color.Red(), color.Green(), color.Blue(), text) -} diff --git a/printer_test.go b/printer_test.go deleted file mode 100644 index a63e380..0000000 --- a/printer_test.go +++ /dev/null @@ -1,85 +0,0 @@ -package clifmt - -import ( - "testing" -) - -func TestColoring(t *testing.T) { - - tests := []struct { - Input string - Output string - }{ - // foreground + background - { - "start ${some text input}(#ff0000:#00ff00) end\n", - "start \033[48;2;0;255;0m\033[38;2;255;0;0msome text input\033[0m\033[0m end\n", - }, { - "start ${some text input}(#f00:#0f0) end\n", - "start \033[48;2;0;255;0m\033[38;2;255;0;0msome text input\033[0m\033[0m end\n", - }, { - "start ${some text input}(red:green) end\n", - "start \033[48;2;0;255;0m\033[38;2;255;0;0msome text input\033[0m\033[0m end\n", - }, - - // mixed notations - { - "start ${some text input}(red:#00ff00) end\n", - "start \033[48;2;0;255;0m\033[38;2;255;0;0msome text input\033[0m\033[0m end\n", - }, { - "start ${some text input}(red:#0f0) end\n", - "start \033[48;2;0;255;0m\033[38;2;255;0;0msome text input\033[0m\033[0m end\n", - }, { - "start ${some text input}(#ff0000:green) end\n", - "start \033[48;2;0;255;0m\033[38;2;255;0;0msome text input\033[0m\033[0m end\n", - }, { - "start ${some text input}(#f00:green) end\n", - "start \033[48;2;0;255;0m\033[38;2;255;0;0msome text input\033[0m\033[0m end\n", - }, - - // foreground only - { - "start ${some text input}(red) end\n", - "start \033[38;2;255;0;0msome text input\033[0m end\n", - }, { - "start ${some text input}(#ff0000) end\n", - "start \033[38;2;255;0;0msome text input\033[0m end\n", - }, { - "start ${some text input}(#f00) end\n", - "start \033[38;2;255;0;0msome text input\033[0m end\n", - }, - - // background only - { - "start ${some text input}(:blue) end\n", - "start \033[48;2;0;0;255msome text input\033[0m end\n", - }, { - "start ${some text input}(:#0000ff) end\n", - "start \033[48;2;0;0;255msome text input\033[0m end\n", - }, { - "start ${some text input}(:#00f) end\n", - "start \033[48;2;0;0;255msome text input\033[0m end\n", - }, - - // multi matches - { - "start ${text1}(red) separation ${text2}(#0f0) end\n", - "start \033[38;2;255;0;0mtext1\033[0m separation \033[38;2;0;255;0mtext2\033[0m end\n", - }, { - "start ${text1}(:red) separation ${text2}(:#0f0) end\n", - "start \033[48;2;255;0;0mtext1\033[0m separation \033[48;2;0;255;0mtext2\033[0m end\n", - }} - - for i, test := range tests { - output, err := Sprintf(test.Input) - if err != nil { - t.Errorf("[%d] unexpected error <%v>", i, err) - break - } - - if output != test.Output { - t.Errorf("[%d] expected '%s', got '%s'", i, test.Output, output) - } - - } -}