├── go.mod ├── .travis.yml ├── README.md ├── LICENSE ├── colorstring_test.go └── colorstring.go /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mitchellh/colorstring 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.0 5 | - 1.1 6 | - 1.2 7 | - 1.3 8 | - tip 9 | 10 | script: 11 | - go test 12 | 13 | matrix: 14 | allow_failures: 15 | - go: tip 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # colorstring [![Build Status](https://travis-ci.org/mitchellh/colorstring.svg)](https://travis-ci.org/mitchellh/colorstring) 2 | 3 | colorstring is a [Go](http://www.golang.org) library for outputting colored 4 | strings to a console using a simple inline syntax in your string to specify 5 | the color to print as. 6 | 7 | For example, the string `[blue]hello [red]world` would output the text 8 | "hello world" in two colors. The API of colorstring allows for easily disabling 9 | colors, adding aliases, etc. 10 | 11 | ## Installation 12 | 13 | Standard `go get`: 14 | 15 | ``` 16 | $ go get github.com/mitchellh/colorstring 17 | ``` 18 | 19 | ## Usage & Example 20 | 21 | For usage and examples see the [Godoc](http://godoc.org/github.com/mitchellh/colorstring). 22 | 23 | Usage is easy enough: 24 | 25 | ```go 26 | colorstring.Println("[blue]Hello [red]World!") 27 | ``` 28 | 29 | Additionally, the `Colorize` struct can be used to set options such as 30 | custom colors, color disabling, etc. 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Mitchell Hashimoto 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /colorstring_test.go: -------------------------------------------------------------------------------- 1 | package colorstring 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | func TestColor(t *testing.T) { 9 | cases := []struct { 10 | Input, Output string 11 | }{ 12 | { 13 | Input: "foo", 14 | Output: "foo", 15 | }, 16 | 17 | { 18 | Input: "[blue]foo", 19 | Output: "\033[34mfoo\033[0m", 20 | }, 21 | 22 | { 23 | Input: "foo[blue]foo", 24 | Output: "foo\033[34mfoo\033[0m", 25 | }, 26 | 27 | { 28 | Input: "foo[what]foo", 29 | Output: "foo[what]foo", 30 | }, 31 | { 32 | Input: "foo[_blue_]foo", 33 | Output: "foo\033[44mfoo\033[0m", 34 | }, 35 | { 36 | Input: "foo[bold]foo", 37 | Output: "foo\033[1mfoo\033[0m", 38 | }, 39 | { 40 | Input: "[blue]foo[bold]bar", 41 | Output: "\033[34mfoo\033[1mbar\033[0m", 42 | }, 43 | { 44 | Input: "[underline]foo[reset]bar", 45 | Output: "\033[4mfoo\033[0mbar\033[0m", 46 | }, 47 | } 48 | 49 | for _, tc := range cases { 50 | actual := Color(tc.Input) 51 | if actual != tc.Output { 52 | t.Errorf( 53 | "Input: %#v\n\nOutput: %#v\n\nExpected: %#v", 54 | tc.Input, 55 | actual, 56 | tc.Output) 57 | } 58 | } 59 | } 60 | 61 | func TestColorPrefix(t *testing.T) { 62 | cases := []struct { 63 | Input, Output string 64 | }{ 65 | { 66 | Input: "foo", 67 | Output: "", 68 | }, 69 | 70 | { 71 | Input: "[blue]foo", 72 | Output: "[blue]", 73 | }, 74 | 75 | { 76 | Input: "[bold][blue]foo", 77 | Output: "[bold][blue]", 78 | }, 79 | 80 | { 81 | Input: " [bold][blue]foo", 82 | Output: "[bold][blue]", 83 | }, 84 | } 85 | 86 | for _, tc := range cases { 87 | actual := ColorPrefix(tc.Input) 88 | if actual != tc.Output { 89 | t.Errorf( 90 | "Input: %#v\n\nOutput: %#v\n\nExpected: %#v", 91 | tc.Input, 92 | actual, 93 | tc.Output) 94 | } 95 | } 96 | } 97 | 98 | func TestColorizeColor_disable(t *testing.T) { 99 | c := def 100 | c.Disable = true 101 | 102 | cases := []struct { 103 | Input, Output string 104 | }{ 105 | { 106 | "[blue]foo", 107 | "foo", 108 | }, 109 | 110 | { 111 | "[foo]bar", 112 | "[foo]bar", 113 | }, 114 | } 115 | 116 | for _, tc := range cases { 117 | actual := c.Color(tc.Input) 118 | if actual != tc.Output { 119 | t.Errorf( 120 | "Input: %#v\n\nOutput: %#v\n\nExpected: %#v", 121 | tc.Input, 122 | actual, 123 | tc.Output) 124 | } 125 | } 126 | } 127 | 128 | func TestColorizeColor_noReset(t *testing.T) { 129 | c := def 130 | c.Reset = false 131 | 132 | input := "[blue]foo" 133 | output := "\033[34mfoo" 134 | actual := c.Color(input) 135 | if actual != output { 136 | t.Errorf( 137 | "Input: %#v\n\nOutput: %#v\n\nExpected: %#v", 138 | input, 139 | actual, 140 | output) 141 | } 142 | } 143 | 144 | func TestConvenienceWrappers(t *testing.T) { 145 | var length int 146 | printInput := "[bold]Print:\t\t[default][red]R[green]G[blue]B[cyan]C[magenta]M[yellow]Y\n" 147 | printlnInput := "[bold]Println:\t[default][red]R[green]G[blue]B[cyan]C[magenta]M[yellow]Y" 148 | printfInput := "[bold]Printf:\t\t[default][red]R[green]G[blue]B[cyan]C[magenta]M[yellow]Y\n" 149 | fprintInput := "[bold]Fprint:\t\t[default][red]R[green]G[blue]B[cyan]C[magenta]M[yellow]Y\n" 150 | fprintlnInput := "[bold]Fprintln:\t[default][red]R[green]G[blue]B[cyan]C[magenta]M[yellow]Y" 151 | fprintfInput := "[bold]Fprintf:\t[default][red]R[green]G[blue]B[cyan]C[magenta]M[yellow]Y\n" 152 | 153 | // colorstring.Print 154 | length, _ = Print(printInput) 155 | assertOutputLength(t, printInput, 58, length) 156 | 157 | // colorstring.Println 158 | length, _ = Println(printlnInput) 159 | assertOutputLength(t, printlnInput, 59, length) 160 | 161 | // colorstring.Printf 162 | length, _ = Printf(printfInput) 163 | assertOutputLength(t, printfInput, 59, length) 164 | 165 | // colorstring.Fprint 166 | length, _ = Fprint(os.Stdout, fprintInput) 167 | assertOutputLength(t, fprintInput, 59, length) 168 | 169 | // colorstring.Fprintln 170 | length, _ = Fprintln(os.Stdout, fprintlnInput) 171 | assertOutputLength(t, fprintlnInput, 60, length) 172 | 173 | // colorstring.Fprintf 174 | length, _ = Fprintf(os.Stdout, fprintfInput) 175 | assertOutputLength(t, fprintfInput, 59, length) 176 | } 177 | 178 | func assertOutputLength(t *testing.T, input string, expectedLength int, actualLength int) { 179 | if actualLength != expectedLength { 180 | t.Errorf("Input: %#v\n\n Output length: %d\n\n Expected: %d", 181 | input, 182 | actualLength, 183 | expectedLength) 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /colorstring.go: -------------------------------------------------------------------------------- 1 | // colorstring provides functions for colorizing strings for terminal 2 | // output. 3 | package colorstring 4 | 5 | import ( 6 | "bytes" 7 | "fmt" 8 | "io" 9 | "regexp" 10 | "strings" 11 | ) 12 | 13 | // Color colorizes your strings using the default settings. 14 | // 15 | // Strings given to Color should use the syntax `[color]` to specify the 16 | // color for text following. For example: `[blue]Hello` will return "Hello" 17 | // in blue. See DefaultColors for all the supported colors and attributes. 18 | // 19 | // If an unrecognized color is given, it is ignored and assumed to be part 20 | // of the string. For example: `[hi]world` will result in "[hi]world". 21 | // 22 | // A color reset is appended to the end of every string. This will reset 23 | // the color of following strings when you output this text to the same 24 | // terminal session. 25 | // 26 | // If you want to customize any of this behavior, use the Colorize struct. 27 | func Color(v string) string { 28 | return def.Color(v) 29 | } 30 | 31 | // ColorPrefix returns the color sequence that prefixes the given text. 32 | // 33 | // This is useful when wrapping text if you want to inherit the color 34 | // of the wrapped text. For example, "[green]foo" will return "[green]". 35 | // If there is no color sequence, then this will return "". 36 | func ColorPrefix(v string) string { 37 | return def.ColorPrefix(v) 38 | } 39 | 40 | // Colorize colorizes your strings, giving you the ability to customize 41 | // some of the colorization process. 42 | // 43 | // The options in Colorize can be set to customize colorization. If you're 44 | // only interested in the defaults, just use the top Color function directly, 45 | // which creates a default Colorize. 46 | type Colorize struct { 47 | // Colors maps a color string to the code for that color. The code 48 | // is a string so that you can use more complex colors to set foreground, 49 | // background, attributes, etc. For example, "boldblue" might be 50 | // "1;34" 51 | Colors map[string]string 52 | 53 | // If true, color attributes will be ignored. This is useful if you're 54 | // outputting to a location that doesn't support colors and you just 55 | // want the strings returned. 56 | Disable bool 57 | 58 | // Reset, if true, will reset the color after each colorization by 59 | // adding a reset code at the end. 60 | Reset bool 61 | } 62 | 63 | // Color colorizes a string according to the settings setup in the struct. 64 | // 65 | // For more details on the syntax, see the top-level Color function. 66 | func (c *Colorize) Color(v string) string { 67 | matches := parseRe.FindAllStringIndex(v, -1) 68 | if len(matches) == 0 { 69 | return v 70 | } 71 | 72 | result := new(bytes.Buffer) 73 | colored := false 74 | m := []int{0, 0} 75 | for _, nm := range matches { 76 | // Write the text in between this match and the last 77 | result.WriteString(v[m[1]:nm[0]]) 78 | m = nm 79 | 80 | var replace string 81 | if code, ok := c.Colors[v[m[0]+1:m[1]-1]]; ok { 82 | colored = true 83 | 84 | if !c.Disable { 85 | replace = fmt.Sprintf("\033[%sm", code) 86 | } 87 | } else { 88 | replace = v[m[0]:m[1]] 89 | } 90 | 91 | result.WriteString(replace) 92 | } 93 | result.WriteString(v[m[1]:]) 94 | 95 | if colored && c.Reset && !c.Disable { 96 | // Write the clear byte at the end 97 | result.WriteString("\033[0m") 98 | } 99 | 100 | return result.String() 101 | } 102 | 103 | // ColorPrefix returns the first color sequence that exists in this string. 104 | // 105 | // For example: "[green]foo" would return "[green]". If no color sequence 106 | // exists, then "" is returned. This is especially useful when wrapping 107 | // colored texts to inherit the color of the wrapped text. 108 | func (c *Colorize) ColorPrefix(v string) string { 109 | return prefixRe.FindString(strings.TrimSpace(v)) 110 | } 111 | 112 | // DefaultColors are the default colors used when colorizing. 113 | // 114 | // If the color is surrounded in underscores, such as "_blue_", then that 115 | // color will be used for the background color. 116 | var DefaultColors map[string]string 117 | 118 | func init() { 119 | DefaultColors = map[string]string{ 120 | // Default foreground/background colors 121 | "default": "39", 122 | "_default_": "49", 123 | 124 | // Foreground colors 125 | "black": "30", 126 | "red": "31", 127 | "green": "32", 128 | "yellow": "33", 129 | "blue": "34", 130 | "magenta": "35", 131 | "cyan": "36", 132 | "light_gray": "37", 133 | "dark_gray": "90", 134 | "light_red": "91", 135 | "light_green": "92", 136 | "light_yellow": "93", 137 | "light_blue": "94", 138 | "light_magenta": "95", 139 | "light_cyan": "96", 140 | "white": "97", 141 | 142 | // Background colors 143 | "_black_": "40", 144 | "_red_": "41", 145 | "_green_": "42", 146 | "_yellow_": "43", 147 | "_blue_": "44", 148 | "_magenta_": "45", 149 | "_cyan_": "46", 150 | "_light_gray_": "47", 151 | "_dark_gray_": "100", 152 | "_light_red_": "101", 153 | "_light_green_": "102", 154 | "_light_yellow_": "103", 155 | "_light_blue_": "104", 156 | "_light_magenta_": "105", 157 | "_light_cyan_": "106", 158 | "_white_": "107", 159 | 160 | // Attributes 161 | "bold": "1", 162 | "dim": "2", 163 | "underline": "4", 164 | "blink_slow": "5", 165 | "blink_fast": "6", 166 | "invert": "7", 167 | "hidden": "8", 168 | 169 | // Reset to reset everything to their defaults 170 | "reset": "0", 171 | "reset_bold": "21", 172 | } 173 | 174 | def = Colorize{ 175 | Colors: DefaultColors, 176 | Reset: true, 177 | } 178 | } 179 | 180 | var def Colorize 181 | var parseReRaw = `\[[a-z0-9_-]+\]` 182 | var parseRe = regexp.MustCompile(`(?i)` + parseReRaw) 183 | var prefixRe = regexp.MustCompile(`^(?i)(` + parseReRaw + `)+`) 184 | 185 | // Print is a convenience wrapper for fmt.Print with support for color codes. 186 | // 187 | // Print formats using the default formats for its operands and writes to 188 | // standard output with support for color codes. Spaces are added between 189 | // operands when neither is a string. It returns the number of bytes written 190 | // and any write error encountered. 191 | func Print(a string) (n int, err error) { 192 | return fmt.Print(Color(a)) 193 | } 194 | 195 | // Println is a convenience wrapper for fmt.Println with support for color 196 | // codes. 197 | // 198 | // Println formats using the default formats for its operands and writes to 199 | // standard output with support for color codes. Spaces are always added 200 | // between operands and a newline is appended. It returns the number of bytes 201 | // written and any write error encountered. 202 | func Println(a string) (n int, err error) { 203 | return fmt.Println(Color(a)) 204 | } 205 | 206 | // Printf is a convenience wrapper for fmt.Printf with support for color codes. 207 | // 208 | // Printf formats according to a format specifier and writes to standard output 209 | // with support for color codes. It returns the number of bytes written and any 210 | // write error encountered. 211 | func Printf(format string, a ...interface{}) (n int, err error) { 212 | return fmt.Printf(Color(format), a...) 213 | } 214 | 215 | // Fprint is a convenience wrapper for fmt.Fprint with support for color codes. 216 | // 217 | // Fprint formats using the default formats for its operands and writes to w 218 | // with support for color codes. Spaces are added between operands when neither 219 | // is a string. It returns the number of bytes written and any write error 220 | // encountered. 221 | func Fprint(w io.Writer, a string) (n int, err error) { 222 | return fmt.Fprint(w, Color(a)) 223 | } 224 | 225 | // Fprintln is a convenience wrapper for fmt.Fprintln with support for color 226 | // codes. 227 | // 228 | // Fprintln formats using the default formats for its operands and writes to w 229 | // with support for color codes. Spaces are always added between operands and a 230 | // newline is appended. It returns the number of bytes written and any write 231 | // error encountered. 232 | func Fprintln(w io.Writer, a string) (n int, err error) { 233 | return fmt.Fprintln(w, Color(a)) 234 | } 235 | 236 | // Fprintf is a convenience wrapper for fmt.Fprintf with support for color 237 | // codes. 238 | // 239 | // Fprintf formats according to a format specifier and writes to w with support 240 | // for color codes. It returns the number of bytes written and any write error 241 | // encountered. 242 | func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) { 243 | return fmt.Fprintf(w, Color(format), a...) 244 | } 245 | --------------------------------------------------------------------------------