pax_global_header00006660000000000000000000000064142204153370014513gustar00rootroot0000000000000052 comment=9b96aa775410a54f7ae205751218688bc097ef26 bunt-1.3.4/000077500000000000000000000000001422041533700124705ustar00rootroot00000000000000bunt-1.3.4/.github/000077500000000000000000000000001422041533700140305ustar00rootroot00000000000000bunt-1.3.4/.github/dependabot.yml000066400000000000000000000001771422041533700166650ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: gomod directory: "/" schedule: interval: daily open-pull-requests-limit: 10 bunt-1.3.4/.github/workflows/000077500000000000000000000000001422041533700160655ustar00rootroot00000000000000bunt-1.3.4/.github/workflows/build.yml000066400000000000000000000030351422041533700177100ustar00rootroot00000000000000--- name: Build and Tests on: push: tags-ignore: - '**' branches: - main pull_request: branches: - main jobs: build: name: Build and Tests runs-on: ubuntu-latest steps: - name: Set up Go uses: actions/setup-go@v2 with: go-version: 1.17.x - name: Check out code into the Go module directory uses: actions/checkout@v2 - name: Verify Go Modules Setup run: | go mod verify - name: Build Go Code run: | go build ./... - name: Sanity Check (go vet) run: | go vet ./... - name: Sanity Check (ineffassign) run: | go get github.com/gordonklaus/ineffassign ineffassign ./... - name: Sanity Check (golint) run: | go get golang.org/x/lint/golint golint ./... - name: Sanity Check (misspell) run: | go get github.com/client9/misspell/cmd/misspell find . -type f | xargs misspell -source=text -error - name: Sanity Check (staticcheck) run: | go get honnef.co/go/tools/cmd/staticcheck staticcheck ./... - name: Run Go Unit Tests run: | go get github.com/onsi/ginkgo/ginkgo github.com/onsi/gomega/... ginkgo -randomizeAllSpecs -randomizeSuites -failOnPending -nodes=1 -compilers=1 -race -trace -cover - name: Upload Code Coverage Profile uses: codecov/codecov-action@v1 with: files: ./*.coverprofile flags: unittests fail_ci_if_error: true verbose: false bunt-1.3.4/.gitignore000066400000000000000000000000171422041533700144560ustar00rootroot00000000000000*.coverprofile bunt-1.3.4/.grenrc.yml000066400000000000000000000002311422041533700145450ustar00rootroot00000000000000--- dataSource: "commits" prefix: "bunt release " includeMessages: "commits" changelogFilename: "CHANGELOG.md" template: commit: "{{url}} {{message}}" bunt-1.3.4/LICENSE000066400000000000000000000020621422041533700134750ustar00rootroot00000000000000MIT License Copyright (c) 2019 The Homeport Team Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. bunt-1.3.4/README.md000066400000000000000000000017161422041533700137540ustar00rootroot00000000000000# bunt [![License](https://img.shields.io/github/license/gonvenience/bunt.svg)](https://github.com/gonvenience/bunt/blob/main/LICENSE) [![Go Report Card](https://goreportcard.com/badge/github.com/gonvenience/bunt)](https://goreportcard.com/report/github.com/gonvenience/bunt) [![Build and Tests](https://github.com/gonvenience/bunt/workflows/Build%20and%20Tests/badge.svg)](https://github.com/gonvenience/bunt/actions?query=workflow%3A%22Build+and+Tests%22) [![Codecov](https://img.shields.io/codecov/c/github/gonvenience/bunt/main.svg)](https://codecov.io/gh/gonvenience/bunt) [![PkgGoDev](https://pkg.go.dev/badge/github.com/gonvenience/bunt)](https://pkg.go.dev/github.com/gonvenience/bunt) [![Release](https://img.shields.io/github/release/gonvenience/bunt.svg)](https://github.com/gonvenience/bunt/releases/latest) ![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/gonvenience/bunt) Golang package for creating true color output in terminals bunt-1.3.4/bunt.go000066400000000000000000000071571422041533700140010ustar00rootroot00000000000000// Copyright © 2019 The Homeport Team // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. package bunt import ( "fmt" "strings" "sync" "github.com/gonvenience/term" ) // Internal bit mask to mark feature states, e.g. foreground coloring const ( fgMask = 0x1 bgMask = 0x2 boldMask = 0x4 italicMask = 0x8 underlineMask = 0x10 ) // ColorSetting defines the coloring setting to be used var ColorSetting SwitchState = SwitchState{value: AUTO} // TrueColorSetting defines the true color usage setting to be used var TrueColorSetting SwitchState = SwitchState{value: AUTO} type state int // Supported setting states const ( AUTO = state(iota) ON OFF ) // SwitchState is the type to cover different preferences/settings like: on, off, or auto type SwitchState struct { sync.Mutex value state } func (s state) String() string { switch s { case ON: return "on" case OFF: return "off" case AUTO: return "auto" } panic("unsupported state") } func (s *SwitchState) String() string { return s.value.String() } // Set updates the switch state based on the provided setting, or fails with an // error in case the setting cannot be parsed func (s *SwitchState) Set(setting string) error { s.Lock() defer s.Unlock() switch strings.ToLower(setting) { case "auto": s.value = AUTO case "off", "no", "false": s.value = OFF case "on", "yes", "true": s.value = ON default: return fmt.Errorf("invalid state '%s' used, supported modes are: auto, on, or off", setting) } return nil } // Type returns the type name of switch state, which is an empty string for now func (s *SwitchState) Type() string { return "" } // UseColors return whether colors are used or not based on the configured color // setting or terminal capabilities func UseColors() bool { ColorSetting.Lock() defer ColorSetting.Unlock() return (ColorSetting.value == ON) || (ColorSetting.value == AUTO && term.IsTerminal() && !term.IsDumbTerminal()) } // UseTrueColor returns whether true color colors should be used or not based on // the configured true color usage setting or terminal capabilities func UseTrueColor() bool { TrueColorSetting.Lock() defer TrueColorSetting.Unlock() return (TrueColorSetting.value == ON) || (TrueColorSetting.value == AUTO && term.IsTrueColor()) } // SetColorSettings is a convenience function to set both color settings at the // same time using the internal locks func SetColorSettings(color state, trueColor state) { ColorSetting.Lock() defer ColorSetting.Unlock() ColorSetting.value = color TrueColorSetting.Lock() defer TrueColorSetting.Unlock() TrueColorSetting.value = trueColor } bunt-1.3.4/bunt_suite_test.go000066400000000000000000000024241422041533700162410ustar00rootroot00000000000000// Copyright © 2019 The Homeport Team // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. package bunt_test import ( "testing" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) func TestBunt(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "bunt suite") } bunt-1.3.4/bunt_test.go000066400000000000000000000042221422041533700150260ustar00rootroot00000000000000// Copyright © 2020 The Homeport Team // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. package bunt_test import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" . "github.com/gonvenience/bunt" ) var _ = Describe("settings tests", func() { var parse = func(setting string) (string, error) { var tmp = &SwitchState{} err := tmp.Set(setting) return tmp.String(), err } Context("parse color settings", func() { It("should parse auto as setting auto", func() { setting, err := parse("auto") Expect(err).ToNot(HaveOccurred()) Expect(setting).To(Equal(AUTO.String())) }) It("should parse off as setting off", func() { setting, err := parse("off") Expect(err).ToNot(HaveOccurred()) Expect(setting).To(Equal(OFF.String())) }) It("should parse on as setting on", func() { setting, err := parse("on") Expect(err).ToNot(HaveOccurred()) Expect(setting).To(Equal(ON.String())) }) It("should fail to parse unknown setting", func() { _, err := parse("foo") Expect(err).To(HaveOccurred()) Expect(err.Error()).To(BeEquivalentTo("invalid state 'foo' used, supported modes are: auto, on, or off")) }) }) }) bunt-1.3.4/colors.go000066400000000000000000000351501422041533700143240ustar00rootroot00000000000000// Copyright © 2019 The Homeport Team // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. package bunt import ( "image/color" "math" "math/rand" "strings" colorful "github.com/lucasb-eyer/go-colorful" ) // The named colors are based upon https://en.wikipedia.org/wiki/Web_colors var ( Pink = hexColor("#FFC0CB") LightPink = hexColor("#FFB6C1") HotPink = hexColor("#FF69B4") DeepPink = hexColor("#FF1493") PaleVioletRed = hexColor("#DB7093") MediumVioletRed = hexColor("#C71585") LightSalmon = hexColor("#FFA07A") Salmon = hexColor("#FA8072") DarkSalmon = hexColor("#E9967A") LightCoral = hexColor("#F08080") IndianRed = hexColor("#CD5C5C") Crimson = hexColor("#DC143C") FireBrick = hexColor("#B22222") DarkRed = hexColor("#8B0000") Red = hexColor("#FF0000") OrangeRed = hexColor("#FF4500") Tomato = hexColor("#FF6347") Coral = hexColor("#FF7F50") DarkOrange = hexColor("#FF8C00") Orange = hexColor("#FFA500") Yellow = hexColor("#FFFF00") LightYellow = hexColor("#FFFFE0") LemonChiffon = hexColor("#FFFACD") LightGoldenrodYellow = hexColor("#FAFAD2") PapayaWhip = hexColor("#FFEFD5") Moccasin = hexColor("#FFE4B5") PeachPuff = hexColor("#FFDAB9") PaleGoldenrod = hexColor("#EEE8AA") Khaki = hexColor("#F0E68C") DarkKhaki = hexColor("#BDB76B") Gold = hexColor("#FFD700") Cornsilk = hexColor("#FFF8DC") BlanchedAlmond = hexColor("#FFEBCD") Bisque = hexColor("#FFE4C4") NavajoWhite = hexColor("#FFDEAD") Wheat = hexColor("#F5DEB3") BurlyWood = hexColor("#DEB887") Tan = hexColor("#D2B48C") RosyBrown = hexColor("#BC8F8F") SandyBrown = hexColor("#F4A460") Goldenrod = hexColor("#DAA520") DarkGoldenrod = hexColor("#B8860B") Peru = hexColor("#CD853F") Chocolate = hexColor("#D2691E") SaddleBrown = hexColor("#8B4513") Sienna = hexColor("#A0522D") Brown = hexColor("#A52A2A") Maroon = hexColor("#800000") DarkOliveGreen = hexColor("#556B2F") Olive = hexColor("#808000") OliveDrab = hexColor("#6B8E23") YellowGreen = hexColor("#9ACD32") LimeGreen = hexColor("#32CD32") Lime = hexColor("#00FF00") LawnGreen = hexColor("#7CFC00") Chartreuse = hexColor("#7FFF00") GreenYellow = hexColor("#ADFF2F") SpringGreen = hexColor("#00FF7F") MediumSpringGreen = hexColor("#00FA9A") LightGreen = hexColor("#90EE90") PaleGreen = hexColor("#98FB98") DarkSeaGreen = hexColor("#8FBC8F") MediumAquamarine = hexColor("#66CDAA") MediumSeaGreen = hexColor("#3CB371") SeaGreen = hexColor("#2E8B57") ForestGreen = hexColor("#228B22") Green = hexColor("#008000") DarkGreen = hexColor("#006400") Aqua = hexColor("#00FFFF") Cyan = hexColor("#00FFFF") LightCyan = hexColor("#E0FFFF") PaleTurquoise = hexColor("#AFEEEE") Aquamarine = hexColor("#7FFFD4") Turquoise = hexColor("#40E0D0") MediumTurquoise = hexColor("#48D1CC") DarkTurquoise = hexColor("#00CED1") LightSeaGreen = hexColor("#20B2AA") CadetBlue = hexColor("#5F9EA0") DarkCyan = hexColor("#008B8B") Teal = hexColor("#008080") LightSteelBlue = hexColor("#B0C4DE") PowderBlue = hexColor("#B0E0E6") LightBlue = hexColor("#ADD8E6") SkyBlue = hexColor("#87CEEB") LightSkyBlue = hexColor("#87CEFA") DeepSkyBlue = hexColor("#00BFFF") DodgerBlue = hexColor("#1E90FF") CornflowerBlue = hexColor("#6495ED") SteelBlue = hexColor("#4682B4") RoyalBlue = hexColor("#4169E1") Blue = hexColor("#0000FF") MediumBlue = hexColor("#0000CD") DarkBlue = hexColor("#00008B") Navy = hexColor("#000080") MidnightBlue = hexColor("#191970") Lavender = hexColor("#E6E6FA") Thistle = hexColor("#D8BFD8") Plum = hexColor("#DDA0DD") Violet = hexColor("#EE82EE") Orchid = hexColor("#DA70D6") Fuchsia = hexColor("#FF00FF") Magenta = hexColor("#FF00FF") MediumOrchid = hexColor("#BA55D3") MediumPurple = hexColor("#9370DB") BlueViolet = hexColor("#8A2BE2") DarkViolet = hexColor("#9400D3") DarkOrchid = hexColor("#9932CC") DarkMagenta = hexColor("#8B008B") Purple = hexColor("#800080") Indigo = hexColor("#4B0082") DarkSlateBlue = hexColor("#483D8B") SlateBlue = hexColor("#6A5ACD") MediumSlateBlue = hexColor("#7B68EE") White = hexColor("#FFFFFF") Snow = hexColor("#FFFAFA") Honeydew = hexColor("#F0FFF0") MintCream = hexColor("#F5FFFA") Azure = hexColor("#F0FFFF") AliceBlue = hexColor("#F0F8FF") GhostWhite = hexColor("#F8F8FF") WhiteSmoke = hexColor("#F5F5F5") Seashell = hexColor("#FFF5EE") Beige = hexColor("#F5F5DC") OldLace = hexColor("#FDF5E6") FloralWhite = hexColor("#FFFAF0") Ivory = hexColor("#FFFFF0") AntiqueWhite = hexColor("#FAEBD7") Linen = hexColor("#FAF0E6") LavenderBlush = hexColor("#FFF0F5") MistyRose = hexColor("#FFE4E1") Gainsboro = hexColor("#DCDCDC") LightGray = hexColor("#D3D3D3") Silver = hexColor("#C0C0C0") DarkGray = hexColor("#A9A9A9") Gray = hexColor("#808080") DimGray = hexColor("#696969") LightSlateGray = hexColor("#778899") SlateGray = hexColor("#708090") DarkSlateGray = hexColor("#2F4F4F") Black = hexColor("#000000") ) var colorByNameMap = map[string]colorful.Color{ "Pink": Pink, "LightPink": LightPink, "HotPink": HotPink, "DeepPink": DeepPink, "PaleVioletRed": PaleVioletRed, "MediumVioletRed": MediumVioletRed, "LightSalmon": LightSalmon, "Salmon": Salmon, "DarkSalmon": DarkSalmon, "LightCoral": LightCoral, "IndianRed": IndianRed, "Crimson": Crimson, "FireBrick": FireBrick, "DarkRed": DarkRed, "Red": Red, "OrangeRed": OrangeRed, "Tomato": Tomato, "Coral": Coral, "DarkOrange": DarkOrange, "Orange": Orange, "Yellow": Yellow, "LightYellow": LightYellow, "LemonChiffon": LemonChiffon, "LightGoldenrodYellow": LightGoldenrodYellow, "PapayaWhip": PapayaWhip, "Moccasin": Moccasin, "PeachPuff": PeachPuff, "PaleGoldenrod": PaleGoldenrod, "Khaki": Khaki, "DarkKhaki": DarkKhaki, "Gold": Gold, "Cornsilk": Cornsilk, "BlanchedAlmond": BlanchedAlmond, "Bisque": Bisque, "NavajoWhite": NavajoWhite, "Wheat": Wheat, "BurlyWood": BurlyWood, "Tan": Tan, "RosyBrown": RosyBrown, "SandyBrown": SandyBrown, "Goldenrod": Goldenrod, "DarkGoldenrod": DarkGoldenrod, "Peru": Peru, "Chocolate": Chocolate, "SaddleBrown": SaddleBrown, "Sienna": Sienna, "Brown": Brown, "Maroon": Maroon, "DarkOliveGreen": DarkOliveGreen, "Olive": Olive, "OliveDrab": OliveDrab, "YellowGreen": YellowGreen, "LimeGreen": LimeGreen, "Lime": Lime, "LawnGreen": LawnGreen, "Chartreuse": Chartreuse, "GreenYellow": GreenYellow, "SpringGreen": SpringGreen, "MediumSpringGreen": MediumSpringGreen, "LightGreen": LightGreen, "PaleGreen": PaleGreen, "DarkSeaGreen": DarkSeaGreen, "MediumAquamarine": MediumAquamarine, "MediumSeaGreen": MediumSeaGreen, "SeaGreen": SeaGreen, "ForestGreen": ForestGreen, "Green": Green, "DarkGreen": DarkGreen, "Aqua": Aqua, "Cyan": Cyan, "LightCyan": LightCyan, "PaleTurquoise": PaleTurquoise, "Aquamarine": Aquamarine, "Turquoise": Turquoise, "MediumTurquoise": MediumTurquoise, "DarkTurquoise": DarkTurquoise, "LightSeaGreen": LightSeaGreen, "CadetBlue": CadetBlue, "DarkCyan": DarkCyan, "Teal": Teal, "LightSteelBlue": LightSteelBlue, "PowderBlue": PowderBlue, "LightBlue": LightBlue, "SkyBlue": SkyBlue, "LightSkyBlue": LightSkyBlue, "DeepSkyBlue": DeepSkyBlue, "DodgerBlue": DodgerBlue, "CornflowerBlue": CornflowerBlue, "SteelBlue": SteelBlue, "RoyalBlue": RoyalBlue, "Blue": Blue, "MediumBlue": MediumBlue, "DarkBlue": DarkBlue, "Navy": Navy, "MidnightBlue": MidnightBlue, "Lavender": Lavender, "Thistle": Thistle, "Plum": Plum, "Violet": Violet, "Orchid": Orchid, "Fuchsia": Fuchsia, "Magenta": Magenta, "MediumOrchid": MediumOrchid, "MediumPurple": MediumPurple, "BlueViolet": BlueViolet, "DarkViolet": DarkViolet, "DarkOrchid": DarkOrchid, "DarkMagenta": DarkMagenta, "Purple": Purple, "Indigo": Indigo, "DarkSlateBlue": DarkSlateBlue, "SlateBlue": SlateBlue, "MediumSlateBlue": MediumSlateBlue, "White": White, "Snow": Snow, "Honeydew": Honeydew, "MintCream": MintCream, "Azure": Azure, "AliceBlue": AliceBlue, "GhostWhite": GhostWhite, "WhiteSmoke": WhiteSmoke, "Seashell": Seashell, "Beige": Beige, "OldLace": OldLace, "FloralWhite": FloralWhite, "Ivory": Ivory, "AntiqueWhite": AntiqueWhite, "Linen": Linen, "LavenderBlush": LavenderBlush, "MistyRose": MistyRose, "Gainsboro": Gainsboro, "LightGray": LightGray, "Silver": Silver, "DarkGray": DarkGray, "Gray": Gray, "DimGray": DimGray, "LightSlateGray": LightSlateGray, "SlateGray": SlateGray, "DarkSlateGray": DarkSlateGray, "Black": Black, } var colorPalette8bit map[uint8]colorful.Color = func() map[uint8]colorful.Color { var rgb = func(r, g, b uint8) colorful.Color { return colorful.Color{ R: float64(r) / 255.0, G: float64(g) / 255.0, B: float64(b) / 255.0, } } palette := make(map[uint8]colorful.Color, 256) // Standard colors palette[0] = rgb(0, 0, 0) // Black palette[1] = rgb(170, 0, 0) // Red palette[2] = rgb(0, 170, 0) // Green palette[3] = rgb(229, 229, 16) // Yellow palette[4] = rgb(0, 0, 170) // Blue palette[5] = rgb(170, 0, 170) // Magenta palette[6] = rgb(0, 170, 170) // Cyan palette[7] = rgb(229, 229, 229) // White // High-intensity colors palette[8] = rgb(85, 85, 85) // Bright Black (Gray) palette[9] = rgb(255, 85, 85) // Bright Red palette[10] = rgb(85, 255, 85) // Bright Green palette[11] = rgb(255, 255, 85) // Bright Yellow palette[12] = rgb(85, 85, 255) // Bright Blue palette[13] = rgb(255, 85, 255) // Bright Magenta palette[14] = rgb(85, 255, 255) // Bright Cyan palette[15] = rgb(255, 255, 255) // Bright White // 216 prepared colors for b := 0; b <= 5; b++ { for g := 0; g <= 5; g++ { for r := 0; r <= 5; r++ { palette[uint8(16+36*r+6*g+b)] = rgb(uint8(r*51), uint8(g*51), uint8(b*51)) } } } // 24 grayscale shades for i := 232; i < 256; i++ { value := uint8(float32(i-232) * (255.0 / 23.0)) palette[uint8(i)] = rgb(value, value, value) } return palette }() func hexColor(scol string) colorful.Color { c, _ := colorful.Hex(scol) return c } func lookupColorByName(colorName string) *colorful.Color { // Try to lookup a color by a supplied hexcode if strings.HasPrefix(colorName, "#") && len(colorName) == 7 { if color, err := colorful.Hex(colorName); err == nil { return &color } } // Try to lookup color by searching in the known colors table if color, ok := colorByNameMap[colorName]; ok { return &color } // Give up return nil } // RandomTerminalFriendlyColors creates a list of random 24 bit colors based on // the 4 bit colors that most terminals support. func RandomTerminalFriendlyColors(n int) []colorful.Color { if n < 0 { panic("size is out of bounds, must be greater than zero") } f := func(i uint8) uint8 { const threshold = 128 if i < threshold { return i } maxFactor := .5 randomFactor := 1 + (rand.Float64()*2*maxFactor - maxFactor) return uint8(math.Max( math.Min( randomFactor*float64(i), 255.0, ), float64(threshold), )) } baseColors := [][]uint8{ {0xAA, 0x00, 0x00}, {0x00, 0xAA, 0x00}, {0xFF, 0xFF, 0x00}, {0x00, 0x00, 0xAA}, {0xAA, 0x00, 0xAA}, {0x00, 0xAA, 0xAA}, {0xAA, 0xAA, 0xAA}, {0xFF, 0x55, 0x55}, {0x55, 0xFF, 0x55}, {0xFF, 0xFF, 0x55}, {0x55, 0x55, 0xFF}, {0xFF, 0x55, 0xFF}, {0x55, 0xFF, 0xFF}, {0xFF, 0xFF, 0xFF}, } result := make([]colorful.Color, n) for i := 0; i < n; i++ { baseColorRGB := baseColors[i%len(baseColors)] r, g, b := baseColorRGB[0], baseColorRGB[1], baseColorRGB[2] color, _ := colorful.MakeColor(color.RGBA{f(r), f(g), f(b), 0xFF}) result[i] = color } return result } bunt-1.3.4/colors_test.go000066400000000000000000000417561422041533700153740ustar00rootroot00000000000000// Copyright © 2019 The Homeport Team // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. package bunt_test import ( "fmt" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" . "github.com/gonvenience/bunt" ) var _ = Describe("color specific tests", func() { Context("fallback to 4 bit colors for non true color terminals", func() { BeforeEach(func() { SetColorSettings(ON, OFF) }) AfterEach(func() { SetColorSettings(AUTO, AUTO) }) var ( f1 = func(color string) string { input := fmt.Sprintf("%s{%s}", color, "text") result, err := ParseString(input, ProcessTextAnnotations()) Expect(err).ToNot(HaveOccurred()) Expect(result).ToNot(BeNil()) return result.String() } f2 = func(color uint8) string { return fmt.Sprintf("\x1b[%dm%s\x1b[0m", color, "text") } ) It("should find a suitable 4 bit equivalent color for both foreground and background", func() { input := "Example: \x1b[38;2;133;247;7mforeground\x1b[0m, and \x1b[48;2;133;247;7mbackground\x1b[0m." result, err := ParseString(input) Expect(err).ToNot(HaveOccurred()) Expect(result).ToNot(BeNil()) Expect(result.String()).To(BeEquivalentTo("Example: \x1b[92mforeground\x1b[0m, and \x1b[102mbackground\x1b[0m.")) }) It("should match known combinations of a true color to a 4 bit color", func() { Expect(f1("Black")).To(BeEquivalentTo(f2(30))) // Black matches Black (#30) Expect(f1("Brown")).To(BeEquivalentTo(f2(31))) // Brown matches Red (#31) Expect(f1("DarkRed")).To(BeEquivalentTo(f2(31))) // DarkRed matches Red (#31) Expect(f1("FireBrick")).To(BeEquivalentTo(f2(31))) // FireBrick matches Red (#31) Expect(f1("Maroon")).To(BeEquivalentTo(f2(31))) // Maroon matches Red (#31) Expect(f1("SaddleBrown")).To(BeEquivalentTo(f2(31))) // SaddleBrown matches Red (#31) Expect(f1("Sienna")).To(BeEquivalentTo(f2(31))) // Sienna matches Red (#31) Expect(f1("DarkGreen")).To(BeEquivalentTo(f2(32))) // DarkGreen matches Green (#32) Expect(f1("DarkSeaGreen")).To(BeEquivalentTo(f2(32))) // DarkSeaGreen matches Green (#32) Expect(f1("ForestGreen")).To(BeEquivalentTo(f2(32))) // ForestGreen matches Green (#32) Expect(f1("Green")).To(BeEquivalentTo(f2(32))) // Green matches Green (#32) Expect(f1("LimeGreen")).To(BeEquivalentTo(f2(32))) // LimeGreen matches Green (#32) Expect(f1("MediumSeaGreen")).To(BeEquivalentTo(f2(32))) // MediumSeaGreen matches Green (#32) Expect(f1("Olive")).To(BeEquivalentTo(f2(32))) // Olive matches Green (#32) Expect(f1("OliveDrab")).To(BeEquivalentTo(f2(32))) // OliveDrab matches Green (#32) Expect(f1("SeaGreen")).To(BeEquivalentTo(f2(32))) // SeaGreen matches Green (#32) Expect(f1("Gold")).To(BeEquivalentTo(f2(33))) // Gold matches Yellow (#33) Expect(f1("Yellow")).To(BeEquivalentTo(f2(33))) // Yellow matches Yellow (#33) Expect(f1("Blue")).To(BeEquivalentTo(f2(34))) // Blue matches Blue (#34) Expect(f1("DarkBlue")).To(BeEquivalentTo(f2(34))) // DarkBlue matches Blue (#34) Expect(f1("DarkSlateBlue")).To(BeEquivalentTo(f2(34))) // DarkSlateBlue matches Blue (#34) Expect(f1("Indigo")).To(BeEquivalentTo(f2(34))) // Indigo matches Blue (#34) Expect(f1("MediumBlue")).To(BeEquivalentTo(f2(34))) // MediumBlue matches Blue (#34) Expect(f1("MidnightBlue")).To(BeEquivalentTo(f2(34))) // MidnightBlue matches Blue (#34) Expect(f1("Navy")).To(BeEquivalentTo(f2(34))) // Navy matches Blue (#34) Expect(f1("BlueViolet")).To(BeEquivalentTo(f2(35))) // BlueViolet matches Magenta (#35) Expect(f1("DarkMagenta")).To(BeEquivalentTo(f2(35))) // DarkMagenta matches Magenta (#35) Expect(f1("DarkOrchid")).To(BeEquivalentTo(f2(35))) // DarkOrchid matches Magenta (#35) Expect(f1("DarkViolet")).To(BeEquivalentTo(f2(35))) // DarkViolet matches Magenta (#35) Expect(f1("MediumVioletRed")).To(BeEquivalentTo(f2(35))) // MediumVioletRed matches Magenta (#35) Expect(f1("Purple")).To(BeEquivalentTo(f2(35))) // Purple matches Magenta (#35) Expect(f1("CadetBlue")).To(BeEquivalentTo(f2(36))) // CadetBlue matches Cyan (#36) Expect(f1("DarkCyan")).To(BeEquivalentTo(f2(36))) // DarkCyan matches Cyan (#36) Expect(f1("DarkTurquoise")).To(BeEquivalentTo(f2(36))) // DarkTurquoise matches Cyan (#36) Expect(f1("DeepSkyBlue")).To(BeEquivalentTo(f2(36))) // DeepSkyBlue matches Cyan (#36) Expect(f1("LightSeaGreen")).To(BeEquivalentTo(f2(36))) // LightSeaGreen matches Cyan (#36) Expect(f1("MediumAquamarine")).To(BeEquivalentTo(f2(36))) // MediumAquamarine matches Cyan (#36) Expect(f1("Teal")).To(BeEquivalentTo(f2(36))) // Teal matches Cyan (#36) Expect(f1("BurlyWood")).To(BeEquivalentTo(f2(37))) // BurlyWood matches White (#37) Expect(f1("DarkGoldenrod")).To(BeEquivalentTo(f2(37))) // DarkGoldenrod matches White (#37) Expect(f1("DarkGray")).To(BeEquivalentTo(f2(37))) // DarkGray matches White (#37) Expect(f1("Gray")).To(BeEquivalentTo(f2(37))) // Gray matches White (#37) Expect(f1("LightPink")).To(BeEquivalentTo(f2(37))) // LightPink matches White (#37) Expect(f1("LightSkyBlue")).To(BeEquivalentTo(f2(37))) // LightSkyBlue matches White (#37) Expect(f1("LightSlateGray")).To(BeEquivalentTo(f2(37))) // LightSlateGray matches White (#37) Expect(f1("LightSteelBlue")).To(BeEquivalentTo(f2(37))) // LightSteelBlue matches White (#37) Expect(f1("Pink")).To(BeEquivalentTo(f2(37))) // Pink matches White (#37) Expect(f1("RosyBrown")).To(BeEquivalentTo(f2(37))) // RosyBrown matches White (#37) Expect(f1("SandyBrown")).To(BeEquivalentTo(f2(37))) // SandyBrown matches White (#37) Expect(f1("Silver")).To(BeEquivalentTo(f2(37))) // Silver matches White (#37) Expect(f1("Tan")).To(BeEquivalentTo(f2(37))) // Tan matches White (#37) Expect(f1("Thistle")).To(BeEquivalentTo(f2(37))) // Thistle matches White (#37) Expect(f1("DarkOliveGreen")).To(BeEquivalentTo(f2(90))) // DarkOliveGreen matches BrightBlack (#90) Expect(f1("DarkSlateGray")).To(BeEquivalentTo(f2(90))) // DarkSlateGray matches BrightBlack (#90) Expect(f1("DimGray")).To(BeEquivalentTo(f2(90))) // DimGray matches BrightBlack (#90) Expect(f1("Chocolate")).To(BeEquivalentTo(f2(91))) // Chocolate matches BrightRed (#91) Expect(f1("Coral")).To(BeEquivalentTo(f2(91))) // Coral matches BrightRed (#91) Expect(f1("Crimson")).To(BeEquivalentTo(f2(91))) // Crimson matches BrightRed (#91) Expect(f1("DarkOrange")).To(BeEquivalentTo(f2(91))) // DarkOrange matches BrightRed (#91) Expect(f1("DarkSalmon")).To(BeEquivalentTo(f2(91))) // DarkSalmon matches BrightRed (#91) Expect(f1("IndianRed")).To(BeEquivalentTo(f2(91))) // IndianRed matches BrightRed (#91) Expect(f1("LightCoral")).To(BeEquivalentTo(f2(91))) // LightCoral matches BrightRed (#91) Expect(f1("LightSalmon")).To(BeEquivalentTo(f2(91))) // LightSalmon matches BrightRed (#91) Expect(f1("OrangeRed")).To(BeEquivalentTo(f2(91))) // OrangeRed matches BrightRed (#91) Expect(f1("PaleVioletRed")).To(BeEquivalentTo(f2(91))) // PaleVioletRed matches BrightRed (#91) Expect(f1("Peru")).To(BeEquivalentTo(f2(91))) // Peru matches BrightRed (#91) Expect(f1("Red")).To(BeEquivalentTo(f2(91))) // Red matches BrightRed (#91) Expect(f1("Salmon")).To(BeEquivalentTo(f2(91))) // Salmon matches BrightRed (#91) Expect(f1("Tomato")).To(BeEquivalentTo(f2(91))) // Tomato matches BrightRed (#91) Expect(f1("Chartreuse")).To(BeEquivalentTo(f2(92))) // Chartreuse matches BrightGreen (#92) Expect(f1("GreenYellow")).To(BeEquivalentTo(f2(92))) // GreenYellow matches BrightGreen (#92) Expect(f1("LawnGreen")).To(BeEquivalentTo(f2(92))) // LawnGreen matches BrightGreen (#92) Expect(f1("LightGreen")).To(BeEquivalentTo(f2(92))) // LightGreen matches BrightGreen (#92) Expect(f1("Lime")).To(BeEquivalentTo(f2(92))) // Lime matches BrightGreen (#92) Expect(f1("MediumSpringGreen")).To(BeEquivalentTo(f2(92))) // MediumSpringGreen matches BrightGreen (#92) Expect(f1("PaleGreen")).To(BeEquivalentTo(f2(92))) // PaleGreen matches BrightGreen (#92) Expect(f1("SpringGreen")).To(BeEquivalentTo(f2(92))) // SpringGreen matches BrightGreen (#92) Expect(f1("YellowGreen")).To(BeEquivalentTo(f2(92))) // YellowGreen matches BrightGreen (#92) Expect(f1("DarkKhaki")).To(BeEquivalentTo(f2(93))) // DarkKhaki matches BrightYellow (#93) Expect(f1("Goldenrod")).To(BeEquivalentTo(f2(93))) // Goldenrod matches BrightYellow (#93) Expect(f1("Khaki")).To(BeEquivalentTo(f2(93))) // Khaki matches BrightYellow (#93) Expect(f1("Orange")).To(BeEquivalentTo(f2(93))) // Orange matches BrightYellow (#93) Expect(f1("PaleGoldenrod")).To(BeEquivalentTo(f2(93))) // PaleGoldenrod matches BrightYellow (#93) Expect(f1("CornflowerBlue")).To(BeEquivalentTo(f2(94))) // CornflowerBlue matches BrightBlue (#94) Expect(f1("DodgerBlue")).To(BeEquivalentTo(f2(94))) // DodgerBlue matches BrightBlue (#94) Expect(f1("MediumPurple")).To(BeEquivalentTo(f2(94))) // MediumPurple matches BrightBlue (#94) Expect(f1("MediumSlateBlue")).To(BeEquivalentTo(f2(94))) // MediumSlateBlue matches BrightBlue (#94) Expect(f1("RoyalBlue")).To(BeEquivalentTo(f2(94))) // RoyalBlue matches BrightBlue (#94) Expect(f1("SlateBlue")).To(BeEquivalentTo(f2(94))) // SlateBlue matches BrightBlue (#94) Expect(f1("SlateGray")).To(BeEquivalentTo(f2(94))) // SlateGray matches BrightBlue (#94) Expect(f1("SteelBlue")).To(BeEquivalentTo(f2(94))) // SteelBlue matches BrightBlue (#94) Expect(f1("DeepPink")).To(BeEquivalentTo(f2(95))) // DeepPink matches BrightMagenta (#95) Expect(f1("Fuchsia")).To(BeEquivalentTo(f2(95))) // Fuchsia matches BrightMagenta (#95) Expect(f1("HotPink")).To(BeEquivalentTo(f2(95))) // HotPink matches BrightMagenta (#95) Expect(f1("Magenta")).To(BeEquivalentTo(f2(95))) // Magenta matches BrightMagenta (#95) Expect(f1("MediumOrchid")).To(BeEquivalentTo(f2(95))) // MediumOrchid matches BrightMagenta (#95) Expect(f1("Orchid")).To(BeEquivalentTo(f2(95))) // Orchid matches BrightMagenta (#95) Expect(f1("Plum")).To(BeEquivalentTo(f2(95))) // Plum matches BrightMagenta (#95) Expect(f1("Violet")).To(BeEquivalentTo(f2(95))) // Violet matches BrightMagenta (#95) Expect(f1("Aqua")).To(BeEquivalentTo(f2(96))) // Aqua matches BrightCyan (#96) Expect(f1("Aquamarine")).To(BeEquivalentTo(f2(96))) // Aquamarine matches BrightCyan (#96) Expect(f1("Cyan")).To(BeEquivalentTo(f2(96))) // Cyan matches BrightCyan (#96) Expect(f1("LightBlue")).To(BeEquivalentTo(f2(96))) // LightBlue matches BrightCyan (#96) Expect(f1("MediumTurquoise")).To(BeEquivalentTo(f2(96))) // MediumTurquoise matches BrightCyan (#96) Expect(f1("PaleTurquoise")).To(BeEquivalentTo(f2(96))) // PaleTurquoise matches BrightCyan (#96) Expect(f1("PowderBlue")).To(BeEquivalentTo(f2(96))) // PowderBlue matches BrightCyan (#96) Expect(f1("SkyBlue")).To(BeEquivalentTo(f2(96))) // SkyBlue matches BrightCyan (#96) Expect(f1("Turquoise")).To(BeEquivalentTo(f2(96))) // Turquoise matches BrightCyan (#96) Expect(f1("AliceBlue")).To(BeEquivalentTo(f2(97))) // AliceBlue matches BrightWhite (#97) Expect(f1("AntiqueWhite")).To(BeEquivalentTo(f2(97))) // AntiqueWhite matches BrightWhite (#97) Expect(f1("Azure")).To(BeEquivalentTo(f2(97))) // Azure matches BrightWhite (#97) Expect(f1("Beige")).To(BeEquivalentTo(f2(97))) // Beige matches BrightWhite (#97) Expect(f1("Bisque")).To(BeEquivalentTo(f2(97))) // Bisque matches BrightWhite (#97) Expect(f1("BlanchedAlmond")).To(BeEquivalentTo(f2(97))) // BlanchedAlmond matches BrightWhite (#97) Expect(f1("Cornsilk")).To(BeEquivalentTo(f2(97))) // Cornsilk matches BrightWhite (#97) Expect(f1("FloralWhite")).To(BeEquivalentTo(f2(97))) // FloralWhite matches BrightWhite (#97) Expect(f1("Gainsboro")).To(BeEquivalentTo(f2(97))) // Gainsboro matches BrightWhite (#97) Expect(f1("GhostWhite")).To(BeEquivalentTo(f2(97))) // GhostWhite matches BrightWhite (#97) Expect(f1("Honeydew")).To(BeEquivalentTo(f2(97))) // Honeydew matches BrightWhite (#97) Expect(f1("Ivory")).To(BeEquivalentTo(f2(97))) // Ivory matches BrightWhite (#97) Expect(f1("Lavender")).To(BeEquivalentTo(f2(97))) // Lavender matches BrightWhite (#97) Expect(f1("LavenderBlush")).To(BeEquivalentTo(f2(97))) // LavenderBlush matches BrightWhite (#97) Expect(f1("LemonChiffon")).To(BeEquivalentTo(f2(97))) // LemonChiffon matches BrightWhite (#97) Expect(f1("LightCyan")).To(BeEquivalentTo(f2(97))) // LightCyan matches BrightWhite (#97) Expect(f1("LightGoldenrodYellow")).To(BeEquivalentTo(f2(97))) // LightGoldenrodYellow matches BrightWhite (#97) Expect(f1("LightGray")).To(BeEquivalentTo(f2(97))) // LightGray matches BrightWhite (#97) Expect(f1("LightYellow")).To(BeEquivalentTo(f2(97))) // LightYellow matches BrightWhite (#97) Expect(f1("Linen")).To(BeEquivalentTo(f2(97))) // Linen matches BrightWhite (#97) Expect(f1("MintCream")).To(BeEquivalentTo(f2(97))) // MintCream matches BrightWhite (#97) Expect(f1("MistyRose")).To(BeEquivalentTo(f2(97))) // MistyRose matches BrightWhite (#97) Expect(f1("Moccasin")).To(BeEquivalentTo(f2(97))) // Moccasin matches BrightWhite (#97) Expect(f1("NavajoWhite")).To(BeEquivalentTo(f2(97))) // NavajoWhite matches BrightWhite (#97) Expect(f1("OldLace")).To(BeEquivalentTo(f2(97))) // OldLace matches BrightWhite (#97) Expect(f1("PapayaWhip")).To(BeEquivalentTo(f2(97))) // PapayaWhip matches BrightWhite (#97) Expect(f1("PeachPuff")).To(BeEquivalentTo(f2(97))) // PeachPuff matches BrightWhite (#97) Expect(f1("Seashell")).To(BeEquivalentTo(f2(97))) // Seashell matches BrightWhite (#97) Expect(f1("Snow")).To(BeEquivalentTo(f2(97))) // Snow matches BrightWhite (#97) Expect(f1("Wheat")).To(BeEquivalentTo(f2(97))) // Wheat matches BrightWhite (#97) Expect(f1("White")).To(BeEquivalentTo(f2(97))) // White matches BrightWhite (#97) Expect(f1("WhiteSmoke")).To(BeEquivalentTo(f2(97))) // WhiteSmoke matches BrightWhite (#97) }) }) Context("custom colors in text annotation", func() { BeforeEach(func() { SetColorSettings(ON, ON) }) AfterEach(func() { SetColorSettings(AUTO, AUTO) }) It("should parse hexcolors in text annotations", func() { Expect(Sprint("#6495ED{CornflowerBlue}")).To( BeEquivalentTo(Sprint("CornflowerBlue{CornflowerBlue}"))) }) }) Context("random colors", func() { BeforeEach(func() { SetColorSettings(ON, OFF) }) AfterEach(func() { SetColorSettings(AUTO, AUTO) }) It("should create a list of random terminal friendly colors", func() { colors := RandomTerminalFriendlyColors(16) Expect(len(colors)).To(BeEquivalentTo(16)) }) It("should panic if negative number is given", func() { Expect(func() { RandomTerminalFriendlyColors(-1) }).To(Panic()) }) }) }) bunt-1.3.4/convenience.go000066400000000000000000000152251422041533700153200ustar00rootroot00000000000000// Copyright © 2019 The Homeport Team // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. package bunt import ( "regexp" "strings" "unicode/utf8" colorful "github.com/lucasb-eyer/go-colorful" ) // StyleOption defines style option for colored strings type StyleOption struct { flags []string postProcess func(*String, map[string]struct{}) } // PlainTextLength returns the length of the input text without any escape // sequences. func PlainTextLength(text string) int { return utf8.RuneCountInString(RemoveAllEscapeSequences(text)) } // RemoveAllEscapeSequences return the input string with all escape sequences // removed. func RemoveAllEscapeSequences(input string) string { escapeSeqFinderRegExp := regexp.MustCompile(`\x1b\[([\d;]*)m`) for loc := escapeSeqFinderRegExp.FindStringIndex(input); loc != nil; loc = escapeSeqFinderRegExp.FindStringIndex(input) { start := loc[0] end := loc[1] input = strings.Replace(input, input[start:end], "", -1) } return input } // Substring returns a substring of a text that may contains escape sequences. // The function will panic in the unlikely case of a parse issue. func Substring(text string, start int, end int) string { result, err := ParseString(text) if err != nil { panic(err) } result.Substring(start, end) return result.String() } // Bold applies the bold text parameter func Bold() StyleOption { return StyleOption{ postProcess: func(s *String, flags map[string]struct{}) { _, skipNewLine := flags["skipNewLine"] for i := range *s { if skipNewLine && (*s)[i].Symbol == '\n' { continue } (*s)[i].Settings |= 1 << 2 } }, } } // Italic applies the italic text parameter func Italic() StyleOption { return StyleOption{ postProcess: func(s *String, flags map[string]struct{}) { _, skipNewLine := flags["skipNewLine"] for i := range *s { if skipNewLine && (*s)[i].Symbol == '\n' { continue } (*s)[i].Settings |= 1 << 3 } }, } } // Underline applies the underline text parameter func Underline() StyleOption { return StyleOption{ postProcess: func(s *String, flags map[string]struct{}) { _, skipNewLine := flags["skipNewLine"] for i := range *s { if skipNewLine && (*s)[i].Symbol == '\n' { continue } (*s)[i].Settings |= 1 << 4 } }, } } // Foreground sets the given color as the foreground color of the text func Foreground(color colorful.Color) StyleOption { return StyleOption{ postProcess: func(s *String, flags map[string]struct{}) { _, skipNewLine := flags["skipNewLine"] _, blendColors := flags["blendColors"] for i := range *s { if skipNewLine && (*s)[i].Symbol == '\n' { continue } r, g, b := color.RGB255() if blendColors { if fgColor := ((*s)[i].Settings >> 8 & 0xFFFFFF); fgColor != 0 { r, g, b = blend(r, g, b, fgColor) } } // reset currently set foreground color (*s)[i].Settings &= 0xFFFFFFFF000000FF (*s)[i].Settings |= 1 (*s)[i].Settings |= uint64(r) << 8 (*s)[i].Settings |= uint64(g) << 16 (*s)[i].Settings |= uint64(b) << 24 } }, } } // ForegroundFunc uses the provided function to set an individual foreground // color for each part of the text, which can be based on the position (x, y) // or the content (rune) in the text. func ForegroundFunc(f func(int, int, rune) *colorful.Color) StyleOption { return StyleOption{ postProcess: func(s *String, flags map[string]struct{}) { _, blendColors := flags["blendColors"] var x, y int for i, c := range *s { if c.Symbol == '\n' { x = 0 y++ continue } if color := f(x, y, c.Symbol); color != nil { r, g, b := color.RGB255() if blendColors { if fgColor := ((*s)[i].Settings >> 8 & 0xFFFFFF); fgColor != 0 { r, g, b = blend(r, g, b, fgColor) } } (*s)[i].Settings &= 0xFFFFFFFF000000FF (*s)[i].Settings |= 1 (*s)[i].Settings |= uint64(r) << 8 (*s)[i].Settings |= uint64(g) << 16 (*s)[i].Settings |= uint64(b) << 24 } x++ } }, } } // EnableTextAnnotations enables post-processing to evaluate text annotations func EnableTextAnnotations() StyleOption { return StyleOption{ postProcess: func(s *String, flags map[string]struct{}) { if err := processTextAnnotations(s); err != nil { panic(err) } }, } } // EachLine enables that new line sequences will be ignored during coloring, // which will lead to strings that are colored line by line and not as a block. func EachLine() StyleOption { return StyleOption{ flags: []string{"skipNewLine"}, } } // Blend enables that applying a color does not completely reset an existing // existing color, but rather mixes/blends both colors together. func Blend() StyleOption { return StyleOption{ flags: []string{"blendColors"}, } } // Style is a multi-purpose function to programmatically apply styles and other // changes to an input text. The function will panic in the unlikely case of a // parse issue. func Style(text string, styleOptions ...StyleOption) string { result, err := ParseString(text) if err != nil { panic(err) } flags := map[string]struct{}{} for _, styleOption := range styleOptions { for _, flag := range styleOption.flags { flags[flag] = struct{}{} } if styleOption.postProcess != nil { styleOption.postProcess(result, flags) } } return result.String() } func blend(r, g, b uint8, currentColor uint64) (uint8, uint8, uint8) { color1 := colorful.Color{ R: float64(r), G: float64(g), B: float64(b), } color2 := colorful.Color{ R: float64((currentColor >> 0) & 0xFF), G: float64((currentColor >> 8) & 0xFF), B: float64((currentColor >> 16) & 0xFF), } return color2.BlendLab(color1, 0.5).RGB255() } bunt-1.3.4/convenience_test.go000066400000000000000000000167571422041533700163720ustar00rootroot00000000000000// Copyright © 2019 The Homeport Team // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. package bunt_test import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" . "github.com/gonvenience/bunt" colorful "github.com/lucasb-eyer/go-colorful" ) var _ = Describe("convenience functions", func() { BeforeEach(func() { SetColorSettings(ON, ON) }) AfterEach(func() { SetColorSettings(AUTO, AUTO) }) Context("substring function", func() { It("should work to correctly cut a string with ANSI sequences", func() { input := Substring("Text: \x1b[1mThis\x1b[0m text is too _long_", 6, 22) expected := "\x1b[1mThis\x1b[0m text is too" Expect(input).To(BeEquivalentTo(expected)) }) It("should panic in case string parsing inside Substring fails", func() { Expect(func() { Substring("\x1b[38;2;1;2mnot ok\x1b[0m", 0, 4) }).To(Panic()) }) }) Context("text length function", func() { It("should return the correct text length of a string with ANSI sequences", func() { Expect(PlainTextLength("\x1b[0;32mINFO \x1b[mNo dependencies found")).To(BeEquivalentTo(len("INFO No dependencies found"))) }) It("should return the right size when used on strings created by the bunt package", func() { Expect(PlainTextLength(Sprintf("*This* text is too long"))).To(BeEquivalentTo(len(Sprintf("This text is too long")))) }) It("should return the correct length based on the rune count", func() { Expect(PlainTextLength("fünf")).To(BeEquivalentTo(4)) }) }) Context("style function", func() { It("should apply bold parameter to a input string", func() { Expect(Style("text", Bold())).To( BeEquivalentTo("\x1b[1mtext\x1b[0m")) }) It("should apply italic parameter to a input string", func() { Expect(Style("text", Italic())).To( BeEquivalentTo("\x1b[3mtext\x1b[0m")) }) It("should apply a custom foreground color to a input string", func() { Expect(Style("text", Foreground(CornflowerBlue))).To( BeEquivalentTo("\x1b[38;2;100;149;237mtext\x1b[0m")) }) It("should apply the bold parameter and a custom foreground color to a input string", func() { Expect(Style("text", Bold(), Foreground(CornflowerBlue))).To( BeEquivalentTo("\x1b[1;38;2;100;149;237mtext\x1b[0m")) }) It("should not evaluate special text annotations by default", func() { Expect(Style("_text_", Foreground(YellowGreen))).To( BeEquivalentTo("\x1b[38;2;154;205;50m_text_\x1b[0m")) }) It("should evaluate special text annotations if enabled", func() { Expect(Style("_text_", Foreground(YellowGreen), EnableTextAnnotations())).To( BeEquivalentTo("\x1b[3;38;2;154;205;50mtext\x1b[0m")) }) It("should support both line by line coloring as well as full block coloring", func() { // By default, color the whole string including new line sequences Expect(Style("text\ntext", Foreground(Yellow))).To( BeEquivalentTo("\x1b[38;2;255;255;0mtext\ntext\x1b[0m")) // If EachLine is enabled before coloring, ignore new line sequences Expect(Style("text\ntext", EachLine(), Foreground(Yellow))).To( BeEquivalentTo("\x1b[38;2;255;255;0mtext\x1b[0m\n\x1b[38;2;255;255;0mtext\x1b[0m")) // If EachLine is enabled after coloring, it has no effect Expect(Style("text\ntext", Foreground(Yellow), EachLine())).To( BeEquivalentTo("\x1b[38;2;255;255;0mtext\ntext\x1b[0m")) }) It("should support text emphasis both line by line as well as full block mode", func() { Expect(Style("text\ntext", Bold())).To( BeEquivalentTo("\x1b[1mtext\ntext\x1b[0m")) Expect(Style("text\ntext", Italic())).To( BeEquivalentTo("\x1b[3mtext\ntext\x1b[0m")) Expect(Style("text\ntext", Underline())).To( BeEquivalentTo("\x1b[4mtext\ntext\x1b[0m")) Expect(Style("text\ntext", EachLine(), Bold())).To( BeEquivalentTo("\x1b[1mtext\x1b[0m\n\x1b[1mtext\x1b[0m")) Expect(Style("text\ntext", EachLine(), Italic())).To( BeEquivalentTo("\x1b[3mtext\x1b[0m\n\x1b[3mtext\x1b[0m")) Expect(Style("text\ntext", EachLine(), Underline())).To( BeEquivalentTo("\x1b[4mtext\x1b[0m\n\x1b[4mtext\x1b[0m")) }) It("should panic in case string parsing inside Style fails", func() { Expect(func() { Style("\x1b[38;2;1;2mnot ok\x1b[0m") }).To(Panic()) }) It("should panic in case a non-existing color", func() { Expect(func() { Style("Foobar{test}", EnableTextAnnotations()) }).To(Panic()) }) It("should correctly apply a color to a string that already contains text emphasis", func() { text := Sprintf("text with *bold* and _italic_.") Expect(Style(text, Foreground(Orange))).To( BeEquivalentTo("\x1b[38;2;255;165;0mtext with \x1b[1;38;2;255;165;0mbold\x1b[0;38;2;255;165;0m and \x1b[3;38;2;255;165;0mitalic\x1b[0;38;2;255;165;0m.\x1b[0m"), ) }) It("should correctly apply a color to a string that already contains coloring", func() { text := Sprintf("text with Green{colored parts}.") Expect(Style(text, Foreground(Red))).To( BeEquivalentTo("\x1b[38;2;255;0;0mtext with colored parts.\x1b[0m"), ) }) It("should correctly blend a color to a string that already contains coloring", func() { text := Sprintf("text with Lime{colored parts}.") Expect(Style(text, Blend(), Foreground(Red))).To( BeEquivalentTo("\x1b[38;2;255;0;0mtext with \x1b[38;2;145;174;136mcolored parts\x1b[38;2;255;0;0m.\x1b[0m"), ) }) It("should set a conditional foreground color based on the respective content", func() { Expect(Style( "foobar\nfOObAr", ForegroundFunc(func(_, _ int, r rune) *colorful.Color { switch r { case 'o': return &Green case 'a': return &Red } return nil })), ).To(Equal("f\x1b[38;2;0;128;0moo\x1b[0mb\x1b[38;2;255;0;0ma\x1b[0mr\nfOObAr")) }) It("should set a conditional foreground color based on the position in the content", func() { Expect(Style( "foobar\nfOObAr", ForegroundFunc(func(x, _ int, _ rune) *colorful.Color { switch x { case 1, 2: return &Green case 4: return &Red } return nil })), ).To(Equal("f\x1b[38;2;0;128;0moo\x1b[0mb\x1b[38;2;255;0;0ma\x1b[0mr\nf\x1b[38;2;0;128;0mOO\x1b[0mb\x1b[38;2;255;0;0mA\x1b[0mr")) }) It("should set a conditional foreground color by blending it with the current color", func() { Expect(Style( Sprintf("Lime{foobar}"), Blend(), ForegroundFunc(func(_, _ int, r rune) *colorful.Color { switch r { case 'o': return &Red } return nil })), ).To(Equal("\x1b[38;2;0;255;0mf\x1b[38;2;145;174;136moo\x1b[38;2;0;255;0mbar\x1b[0m")) }) }) }) bunt-1.3.4/error.go000066400000000000000000000025311422041533700141510ustar00rootroot00000000000000// Copyright © 2019 The Homeport Team // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. package bunt import "fmt" // Errorf wraps fmt.Errorf(err error, format string, a ...interface{}) and evaluates any text markers into its respective format func Errorf(format string, a ...interface{}) error { return fmt.Errorf(Sprintf(format, a...)) } bunt-1.3.4/error_test.go000066400000000000000000000031641422041533700152130ustar00rootroot00000000000000// Copyright © 2019 The Homeport Team // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. package bunt_test import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" . "github.com/gonvenience/bunt" ) var _ = Describe("error functions", func() { Context("process markdown style in Errorf function", func() { BeforeEach(func() { SetColorSettings(ON, OFF) }) AfterEach(func() { SetColorSettings(AUTO, AUTO) }) It("should parse and process markdown style in Errorf", func() { Expect(Errorf("failed to start *%s*", "*X*").Error()).To(BeEquivalentTo("failed to start \x1b[1m*X*\x1b[0m")) }) }) }) bunt-1.3.4/go.mod000066400000000000000000000014411422041533700135760ustar00rootroot00000000000000module github.com/gonvenience/bunt go 1.17 require ( github.com/gonvenience/term v1.0.2 github.com/lucasb-eyer/go-colorful v1.2.0 github.com/mattn/go-ciede2000 v0.0.0-20170301095244-782e8c62fec3 github.com/onsi/ginkgo v1.16.5 github.com/onsi/gomega v1.19.0 ) require ( github.com/fsnotify/fsnotify v1.5.1 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect github.com/nxadm/tail v1.4.8 // indirect golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/text v0.3.7 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) bunt-1.3.4/go.sum000066400000000000000000000275311422041533700136330ustar00rootroot00000000000000github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/gonvenience/term v1.0.2 h1:qKa2RydbWIrabGjR/fegJwpW5m+JvUwFL8mLhHzDXn0= github.com/gonvenience/term v1.0.2/go.mod h1:wThTR+3MzWtWn7XGVW6qQ65uaVf8GHED98KmwpuEQeo= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-ciede2000 v0.0.0-20170301095244-782e8c62fec3 h1:BXxTozrOU8zgC5dkpn3J6NTRdoP+hjok/e+ACr4Hibk= github.com/mattn/go-ciede2000 v0.0.0-20170301095244-782e8c62fec3/go.mod h1:x1uk6vxTiVuNt6S5R2UYgdhpj3oKojXvOXauHZ7dEnI= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/ginkgo/v2 v2.1.3 h1:e/3Cwtogj0HA+25nMP1jCMDIf8RtRYbGwGGuBIFztkc= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.18.0/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20220121210141-e204ce36a2ba/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= bunt-1.3.4/model.go000066400000000000000000000034501422041533700141210ustar00rootroot00000000000000// Copyright © 2019 The Homeport Team // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. package bunt // String is a string with color information type String []ColoredRune // ColoredRune is a rune with additional color information. // // Bit details: // - 1st bit, foreground color on/off // - 2nd bit, background color on/off // - 3rd bit, bold on/off // - 4th bit, italic on/off // - 5th bit, underline on/off // - 6th-8th bit, unused/reserved // - 9th-32nd bit, 24 bit RGB foreground color // - 33rd-56th bit, 24 bit RGB background color // - 57th-64th bit, unused/reserved type ColoredRune struct { Symbol rune Settings uint64 } // Substring cuts the String to a sub-string using the provided absolute start // and end indicies. func (s *String) Substring(from, to int) { *s = (*s)[from:to] } bunt-1.3.4/model_test.go000066400000000000000000000037501422041533700151630ustar00rootroot00000000000000// Copyright © 2019 The Homeport Team // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. package bunt_test import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" . "github.com/gonvenience/bunt" ) var _ = Describe("working with colored strings", func() { Context("cut a substring from a colored string", func() { BeforeEach(func() { SetColorSettings(ON, OFF) }) AfterEach(func() { SetColorSettings(AUTO, AUTO) }) It("should be possible to cut out a substring from a colored string", func() { input := "Example: \x1b[1mbold\x1b[0m, \x1b[3mitalic\x1b[0m, \x1b[4munderline\x1b[0m, \x1b[38;2;133;247;7mforeground\x1b[0m, and \x1b[48;2;133;247;7mbackground\x1b[0m." result, err := ParseString(input) Expect(err).ToNot(HaveOccurred()) Expect(result).ToNot(BeNil()) result.Substring(11, 28) expected := "\x1b[1mld\x1b[0m, \x1b[3mitalic\x1b[0m, \x1b[4munder\x1b[0m" actual := result.String() Expect(actual).To(BeEquivalentTo(expected)) }) }) }) bunt-1.3.4/parse.go000066400000000000000000000310151422041533700141310ustar00rootroot00000000000000// Copyright © 2019 The Homeport Team // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. package bunt import ( "bufio" "bytes" "fmt" "io" "regexp" "sort" "strconv" "strings" ) var ( escapeSeqRegExp = regexp.MustCompile(`\x1b\[(\d+(;\d+)*)m`) boldMarker = regexp.MustCompile(`\*([^*]+?)\*`) italicMarker = regexp.MustCompile(`_([^_]+?)_`) underlineMarker = regexp.MustCompile(`~([^~]+?)~`) colorMarker = regexp.MustCompile(`(#?\w+)\{([^}]+?)\}`) ) // ParseOption defines parser options type ParseOption func(*String) error // ProcessTextAnnotations specifies whether during parsing bunt-specific text // annotations like *bold*, or _italic_ should be processed. func ProcessTextAnnotations() ParseOption { return func(s *String) error { return processTextAnnotations(s) } } // ParseStream reads from the input reader and parses all supported ANSI // sequences that are relevant for colored strings. // // Please notes: This function is still under development and should replace // the `ParseString` function code eventually. func ParseStream(in io.Reader, opts ...ParseOption) (*String, error) { var input *bufio.Reader switch typed := in.(type) { case *bufio.Reader: input = typed default: input = bufio.NewReader(in) } type seq struct { values string suffix rune } var readSGR = func() seq { var buf bytes.Buffer for { r, _, err := input.ReadRune() if err == io.EOF { break } switch r { case 'h', 'l', 'm', 'r', 'A', 'B', 'C', 'D', 'H', 'f', 'g', 'K', 'J', 'y', 'q': return seq{ values: buf.String(), suffix: r, } default: buf.WriteRune(r) } } panic("failed to parse ANSI sequence") } var skipUntil = func(end rune) { for { r, _, err := input.ReadRune() if err == io.EOF { panic("reached end of file before reaching end identifier") } if r == end { return } } } var asUint = func(s string) uint { tmp, err := strconv.ParseUint(s, 10, 8) if err != nil { panic(err) } return uint(tmp) } var result String var line String var lineIdx uint var lineCap uint var settings uint64 var readANSISeq = func() { r, _, err := input.ReadRune() if err == io.EOF { return } // https://en.wikipedia.org/wiki/ANSI_escape_code#Escape_sequences switch r { case '[': var seq = readSGR() switch seq.suffix { case 'm': // colors settings, err = parseSelectGraphicRenditionEscapeSequence(seq.values) if err != nil { panic(err) } case 'D': // move cursor left n lines n := asUint(seq.values) if n > lineIdx { panic("move cursor value is out of bounds") } lineIdx -= n case 'K': // clear line var start, end uint switch seq.values { case "", "0": start, end = lineIdx, lineCap case "1": start, end = 0, lineIdx case "2": start, end = 0, lineCap } for i := start; i < end; i++ { line[i].Symbol = ' ' line[i].Settings = 0 } default: // ignoring all other sequences } case ']': skipUntil('\a') } } var add = func(r rune) { var cr = ColoredRune{Symbol: r, Settings: settings} if lineIdx < lineCap { line[lineIdx] = cr lineIdx++ } else { line = append(line, cr) lineIdx++ lineCap++ } } var del = func() { line = line[:len(line)-1] lineIdx-- lineCap-- } var flush = func() { // Prepare to remove trailing spaces by finding the last non-space rune var endIdx = len(line) - 1 for ; endIdx >= 0; endIdx-- { if line[endIdx].Symbol != ' ' { break } } result = append(result, line[:endIdx+1]...) line = String{} lineIdx = 0 lineCap = 0 } var newline = func() { flush() result = append(result, ColoredRune{Symbol: '\n'}) } for { r, _, err := input.ReadRune() if err != nil && err == io.EOF { break } switch r { case '\x1b': readANSISeq() case '\r': lineIdx = 0 case '\n': newline() case '\b': del() default: add(r) } } flush() // Process optional parser options for _, opt := range opts { if err := opt(&result); err != nil { return nil, err } } return &result, nil } // ParseString parses a string that can contain both ANSI escape code Select // Graphic Rendition (SGR) codes and Markdown style text annotations, for // example *bold* or _italic_. // SGR details : https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters func ParseString(input string, opts ...ParseOption) (*String, error) { var ( pointer int current uint64 err error ) // Special case: the escape sequence without any parameter is equivalent to // the reset escape sequence. input = strings.Replace(input, "\x1b[m", "\x1b[0m", -1) // Ignore 'Set cursor key to application' sequence input = strings.Replace(input, "\x1b[?1h", "", -1) // Ignore keypad mode settings input = strings.Replace(input, "\x1b=", "", -1) input = strings.Replace(input, "\x1b>", "", -1) // Ignore clear line from cursor right input = strings.Replace(input, "\x1b[K", "", -1) // Ignore known mode settings input = regexp.MustCompile(`\x1b\[\?.+[lh]`).ReplaceAllString(input, "") // Ignore this unknown sequence, which seems to be an conditional check input = regexp.MustCompile(`\x1b\]11;\?.`).ReplaceAllString(input, "") var result String var applyToResult = func(str string, mask uint64) { for _, r := range str { result = append(result, ColoredRune{r, mask}) } } for _, submatch := range escapeSeqRegExp.FindAllStringSubmatchIndex(input, -1) { fullMatchStart, fullMatchEnd := submatch[0], submatch[1] settingsStart, settingsEnd := submatch[2], submatch[3] applyToResult(input[pointer:fullMatchStart], current) current, err = parseSelectGraphicRenditionEscapeSequence(input[settingsStart:settingsEnd]) if err != nil { return nil, err } pointer = fullMatchEnd } // Flush the remaining input string part into the result applyToResult(input[pointer:], current) // Process optional parser options for _, opt := range opts { if err = opt(&result); err != nil { return nil, err } } return &result, nil } func parseSelectGraphicRenditionEscapeSequence(escapeSeq string) (uint64, error) { values := []uint8{} for _, x := range strings.Split(escapeSeq, ";") { // Note: This only works, because of the regular expression only matching // digits. Therefore, it should be okay to omit the error. value, _ := strconv.Atoi(x) values = append(values, uint8(value)) } result := uint64(0) for i := 0; i < len(values); i++ { switch values[i] { case 1: // bold result |= boldMask case 3: // italic result |= italicMask case 4: // underline result |= underlineMask case 30: // Black result |= fgRGBMask(1, 1, 1) case 31: // Red result |= fgRGBMask(222, 56, 43) case 32: // Green result |= fgRGBMask(57, 181, 74) case 33: // Yellow result |= fgRGBMask(255, 199, 6) case 34: // Blue result |= fgRGBMask(0, 111, 184) case 35: // Magenta result |= fgRGBMask(118, 38, 113) case 36: // Cyan result |= fgRGBMask(44, 181, 233) case 37: // White result |= fgRGBMask(204, 204, 204) case 90: // Bright Black (Gray) result |= fgRGBMask(128, 128, 128) case 91: // Bright Red result |= fgRGBMask(255, 0, 0) case 92: // Bright Green result |= fgRGBMask(0, 255, 0) case 93: // Bright Yellow result |= fgRGBMask(255, 255, 0) case 94: // Bright Blue result |= fgRGBMask(0, 0, 255) case 95: // Bright Magenta result |= fgRGBMask(255, 0, 255) case 96: // Bright Cyan result |= fgRGBMask(0, 255, 255) case 97: // Bright White result |= fgRGBMask(255, 255, 255) case 40: // Black result |= bgRGBMask(1, 1, 1) case 41: // Red result |= bgRGBMask(222, 56, 43) case 42: // Green result |= bgRGBMask(57, 181, 74) case 43: // Yellow result |= bgRGBMask(255, 199, 6) case 44: // Blue result |= bgRGBMask(0, 111, 184) case 45: // Magenta result |= bgRGBMask(118, 38, 113) case 46: // Cyan result |= bgRGBMask(44, 181, 233) case 47: // White result |= bgRGBMask(204, 204, 204) case 100: // Bright Black (Gray) result |= bgRGBMask(128, 128, 128) case 101: // Bright Red result |= bgRGBMask(255, 0, 0) case 102: // Bright Green result |= bgRGBMask(0, 255, 0) case 103: // Bright Yellow result |= bgRGBMask(255, 255, 0) case 104: // Bright Blue result |= bgRGBMask(0, 0, 255) case 105: // Bright Magenta result |= bgRGBMask(255, 0, 255) case 106: // Bright Cyan result |= bgRGBMask(0, 255, 255) case 107: // Bright White result |= bgRGBMask(255, 255, 255) case 38: // foreground color switch { case len(values) > 4 && values[i+1] == 2: result |= fgRGBMask(uint64(values[i+2]), uint64(values[i+3]), uint64(values[i+4])) i += 4 case len(values) > 2 && values[i+1] == 5: r, g, b := lookUp8bitColor(values[i+2]) result |= fgRGBMask(uint64(r), uint64(g), uint64(b)) i += 2 default: return 0, fmt.Errorf("unsupported foreground color selection '%v'", values) } case 48: // background color switch { case len(values) > 4 && values[i+1] == 2: result |= bgRGBMask(uint64(values[i+2]), uint64(values[i+3]), uint64(values[i+4])) i += 4 case len(values) > 2 && values[i+1] == 5: r, g, b := lookUp8bitColor(values[i+2]) result |= bgRGBMask(uint64(r), uint64(g), uint64(b)) i += 2 default: return 0, fmt.Errorf("unsupported background color selection '%v'", values) } } } return result, nil } func processTextAnnotations(text *String) error { var buffer bytes.Buffer for _, coloredRune := range *text { _ = buffer.WriteByte(byte(coloredRune.Symbol)) } raw := buffer.String() toBeDeleted := []int{} // Process text annotation markers for bold, italic and underline helperMap := map[uint64]*regexp.Regexp{ boldMask: boldMarker, italicMask: italicMarker, underlineMask: underlineMarker, } for mask, regex := range helperMap { for _, match := range regex.FindAllStringSubmatchIndex(raw, -1) { fullMatchStart, fullMatchEnd := match[0], match[1] textStart, textEnd := match[2], match[3] for i := textStart; i < textEnd; i++ { (*text)[i].Settings |= mask } toBeDeleted = append(toBeDeleted, fullMatchStart, fullMatchEnd-1) } } // Process text annotation markers that specify a foreground color for a // specific part of the text for _, match := range colorMarker.FindAllStringSubmatchIndex(raw, -1) { fullMatchStart, fullMatchEnd := match[0], match[1] colorName := raw[match[2]:match[3]] textStart, textEnd := match[4], match[5] color := lookupColorByName(colorName) if color == nil { return fmt.Errorf("unable to find color by name: %s", colorName) } r, g, b := color.RGB255() for i := textStart; i < textEnd; i++ { (*text)[i].Settings |= fgMask (*text)[i].Settings |= uint64(r) << 8 (*text)[i].Settings |= uint64(g) << 16 (*text)[i].Settings |= uint64(b) << 24 } for i := fullMatchStart; i < fullMatchEnd; i++ { if i < textStart || i > textEnd-1 { toBeDeleted = append(toBeDeleted, i) } } } // Finally, sort the runes to be deleted in descending order and delete them // one by one to get rid of the text annotation markers sort.Slice(toBeDeleted, func(i, j int) bool { return toBeDeleted[i] > toBeDeleted[j] }) for _, idx := range toBeDeleted { (*text) = append((*text)[:idx], (*text)[idx+1:]...) } return nil } func fgRGBMask(r, g, b uint64) uint64 { return fgMask | r<<8 | g<<16 | b<<24 } func bgRGBMask(r, g, b uint64) uint64 { return bgMask | r<<32 | g<<40 | b<<48 } func lookUp8bitColor(n uint8) (r, g, b uint8) { return colorPalette8bit[n].RGB255() } bunt-1.3.4/parse_test.go000066400000000000000000000171451422041533700152000ustar00rootroot00000000000000// Copyright © 2019 The Homeport Team // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. package bunt_test import ( "strings" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" . "github.com/gonvenience/bunt" ) var _ = Describe("parse input string", func() { BeforeEach(func() { SetColorSettings(ON, ON) }) AfterEach(func() { SetColorSettings(AUTO, AUTO) }) Context("parse supported ANSI sequences from an input reader", func() { It("should parse an example command line input without errors", func() { example := "Hallo\rHello, \x1b[1mWorld\x1b[0m!" result, err := ParseStream(strings.NewReader(example)) Expect(err).ToNot(HaveOccurred()) Expect(result.String()).To(Equal("Hello, \x1b[1mWorld\x1b[0m!")) }) It("should parse escape sequences that move the cursor in the row", func() { example := "Hallo\x1b[5DHello, \x1b[1mWorld\x1b[0m!" result, err := ParseStream(strings.NewReader(example)) Expect(err).ToNot(HaveOccurred()) Expect(result.String()).To(Equal("Hello, \x1b[1mWorld\x1b[0m!")) }) }) Context("parse Select Graphic Rendition (SGR) based input", func() { It("should parse an input string with SGR parameters", func() { input := "Example: \x1b[1mbold\x1b[0m, \x1b[3mitalic\x1b[0m, \x1b[4munderline\x1b[0m, \x1b[38;2;133;247;7mforeground\x1b[0m, and \x1b[48;2;133;247;7mbackground\x1b[0m." result, err := ParseString(input) Expect(err).ToNot(HaveOccurred()) Expect(*result).To( BeEquivalentTo(String([]ColoredRune{ {'E', 0x0000000000000000}, {'x', 0x0000000000000000}, {'a', 0x0000000000000000}, {'m', 0x0000000000000000}, {'p', 0x0000000000000000}, {'l', 0x0000000000000000}, {'e', 0x0000000000000000}, {':', 0x0000000000000000}, {' ', 0x0000000000000000}, {'b', 0x0000000000000004}, {'o', 0x0000000000000004}, {'l', 0x0000000000000004}, {'d', 0x0000000000000004}, {',', 0x0000000000000000}, {' ', 0x0000000000000000}, {'i', 0x0000000000000008}, {'t', 0x0000000000000008}, {'a', 0x0000000000000008}, {'l', 0x0000000000000008}, {'i', 0x0000000000000008}, {'c', 0x0000000000000008}, {',', 0x0000000000000000}, {' ', 0x0000000000000000}, {'u', 0x0000000000000010}, {'n', 0x0000000000000010}, {'d', 0x0000000000000010}, {'e', 0x0000000000000010}, {'r', 0x0000000000000010}, {'l', 0x0000000000000010}, {'i', 0x0000000000000010}, {'n', 0x0000000000000010}, {'e', 0x0000000000000010}, {',', 0x0000000000000000}, {' ', 0x0000000000000000}, {'f', 0x0000000007F78501}, {'o', 0x0000000007F78501}, {'r', 0x0000000007F78501}, {'e', 0x0000000007F78501}, {'g', 0x0000000007F78501}, {'r', 0x0000000007F78501}, {'o', 0x0000000007F78501}, {'u', 0x0000000007F78501}, {'n', 0x0000000007F78501}, {'d', 0x0000000007F78501}, {',', 0x0000000000000000}, {' ', 0x0000000000000000}, {'a', 0x0000000000000000}, {'n', 0x0000000000000000}, {'d', 0x0000000000000000}, {' ', 0x0000000000000000}, {'b', 0x0007F78500000002}, {'a', 0x0007F78500000002}, {'c', 0x0007F78500000002}, {'k', 0x0007F78500000002}, {'g', 0x0007F78500000002}, {'r', 0x0007F78500000002}, {'o', 0x0007F78500000002}, {'u', 0x0007F78500000002}, {'n', 0x0007F78500000002}, {'d', 0x0007F78500000002}, {'.', 0x0000000000000000}, }))) }) It("should bail out nicely in case of invalid 24 bit foreground color parameters", func() { input := "Invalid: \x1b[38;2;1;2mfoobar\x1b[0m." result, err := ParseString(input) Expect(err).To(HaveOccurred()) Expect(result).To(BeNil()) }) It("should bail out nicely in case of invalid 24 bit background color parameters", func() { input := "Invalid: \x1b[48;2;1;2mfoobar\x1b[0m." result, err := ParseString(input) Expect(err).To(HaveOccurred()) Expect(result).To(BeNil()) }) }) Context("parse markdown style text annotations", func() { It("should parse an input string with markdown style text annotations", func() { input := "Example: *bold*, _italic_, ~underline~, and CornflowerBlue{foreground}." result, err := ParseString(input, ProcessTextAnnotations()) Expect(err).ToNot(HaveOccurred()) Expect(*result).To( BeEquivalentTo(String([]ColoredRune{ {'E', 0x0000000000000000}, {'x', 0x0000000000000000}, {'a', 0x0000000000000000}, {'m', 0x0000000000000000}, {'p', 0x0000000000000000}, {'l', 0x0000000000000000}, {'e', 0x0000000000000000}, {':', 0x0000000000000000}, {' ', 0x0000000000000000}, {'b', 0x0000000000000004}, {'o', 0x0000000000000004}, {'l', 0x0000000000000004}, {'d', 0x0000000000000004}, {',', 0x0000000000000000}, {' ', 0x0000000000000000}, {'i', 0x0000000000000008}, {'t', 0x0000000000000008}, {'a', 0x0000000000000008}, {'l', 0x0000000000000008}, {'i', 0x0000000000000008}, {'c', 0x0000000000000008}, {',', 0x0000000000000000}, {' ', 0x0000000000000000}, {'u', 0x0000000000000010}, {'n', 0x0000000000000010}, {'d', 0x0000000000000010}, {'e', 0x0000000000000010}, {'r', 0x0000000000000010}, {'l', 0x0000000000000010}, {'i', 0x0000000000000010}, {'n', 0x0000000000000010}, {'e', 0x0000000000000010}, {',', 0x0000000000000000}, {' ', 0x0000000000000000}, {'a', 0x0000000000000000}, {'n', 0x0000000000000000}, {'d', 0x0000000000000000}, {' ', 0x0000000000000000}, {'f', 0x00000000ED956401}, {'o', 0x00000000ED956401}, {'r', 0x00000000ED956401}, {'e', 0x00000000ED956401}, {'g', 0x00000000ED956401}, {'r', 0x00000000ED956401}, {'o', 0x00000000ED956401}, {'u', 0x00000000ED956401}, {'n', 0x00000000ED956401}, {'d', 0x00000000ED956401}, {'.', 0x0000000000000000}, }))) }) It("should bail out nicely in case of invalid color name", func() { input := "Invalid: InvalidColor{foobar}." result, err := ParseString(input, ProcessTextAnnotations()) Expect(err).To(HaveOccurred()) Expect(result).To(BeNil()) }) }) Context("parse input string with multi-byte characters", func() { It("should correctly parse multi-byte character strings", func() { input := "Gray{Debug➤ Found asset file} White{X} with permission White{Y}" result, err := ParseString(input, ProcessTextAnnotations()) Expect(err).ToNot(HaveOccurred()) Expect(result).ToNot(BeNil()) Expect(result.String()).To( BeEquivalentTo("\x1b[38;2;128;128;128mDebug➤ Found asset file\x1b[0m \x1b[38;2;255;255;255mX\x1b[0m with permission \x1b[38;2;255;255;255mY\x1b[0m")) }) }) }) bunt-1.3.4/print.go000066400000000000000000000067511422041533700141640ustar00rootroot00000000000000// Copyright © 2019 The Homeport Team // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. package bunt import ( "fmt" "io" ) func evaluateInputs(in ...interface{}) []interface{} { result := make([]interface{}, len(in)) for i, x := range in { switch obj := x.(type) { case string: result[i] = evaluateString(obj) default: result[i] = obj } } return result } func evaluateString(input string) string { if result, err := ParseString(input, ProcessTextAnnotations()); err == nil { return result.String() } return input } // Print wraps fmt.Print(a ...interface{}) and evaluates any text markers into its respective format func Print(a ...interface{}) (n int, err error) { return fmt.Print(evaluateInputs(a...)...) } // Printf wraps fmt.Printf(format string, a ...interface{}) and evaluates any text markers into its respective format func Printf(format string, a ...interface{}) (n int, err error) { return fmt.Printf(evaluateString(format), a...) } // Println wraps fmt.Println(a ...interface{}) and evaluates any text markers into its respective format func Println(a ...interface{}) (n int, err error) { return fmt.Println(evaluateInputs(a...)...) } // Fprint wraps fmt.Fprint(w io.Writer, a ...interface{}) and evaluates any text markers into its respective format func Fprint(w io.Writer, a ...interface{}) (n int, err error) { return fmt.Fprint(w, evaluateInputs(a...)...) } // Fprintf wraps fmt.Fprintf(w io.Writer, format string, a ...interface{}) and evaluates any text markers into its respective format func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) { return fmt.Fprintf(w, evaluateString(format), a...) } // Fprintln wraps fmt.Fprintln(w io.Writer, a ...interface{}) and evaluates any text markers into its respective format func Fprintln(w io.Writer, a ...interface{}) (n int, err error) { return fmt.Fprintln(w, evaluateInputs(a...)...) } // Sprint wraps fmt.Sprint(a ...interface{}) and evaluates any text markers into its respective format func Sprint(a ...interface{}) string { return fmt.Sprint(evaluateInputs(a...)...) } // Sprintf wraps fmt.Sprintf(format string, a ...interface{}) and evaluates any text markers into its respective format func Sprintf(format string, a ...interface{}) string { return fmt.Sprintf(evaluateString(format), a...) } // Sprintln wraps fmt.Sprintln(a ...interface{}) and evaluates any text markers into its respective format func Sprintln(a ...interface{}) string { return fmt.Sprintln(evaluateInputs(a...)...) } bunt-1.3.4/print_test.go000066400000000000000000000112171422041533700152140ustar00rootroot00000000000000// Copyright © 2019 The Homeport Team // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. package bunt_test import ( "bufio" "bytes" "io" "os" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" . "github.com/gonvenience/bunt" ) var _ = Describe("print functions", func() { Context("process markdown style in Print functions", func() { var captureStdout = func(f func()) string { r, w, err := os.Pipe() Expect(err).ToNot(HaveOccurred()) tmp := os.Stdout defer func() { os.Stdout = tmp }() os.Stdout = w f() w.Close() var buf bytes.Buffer _, err = io.Copy(&buf, r) Expect(err).ToNot(HaveOccurred()) return buf.String() } BeforeEach(func() { SetColorSettings(ON, AUTO) }) AfterEach(func() { SetColorSettings(AUTO, AUTO) }) It("should parse and process markdown style in Print", func() { Expect(captureStdout(func() { _, _ = Print("This should be *bold*.") })).To(BeEquivalentTo("This should be \x1b[1mbold\x1b[0m.")) }) It("should parse and process markdown style in Printf", func() { Expect(captureStdout(func() { _, _ = Printf("This should be *%s*.", "bold") })).To(BeEquivalentTo("This should be \x1b[1mbold\x1b[0m.")) }) It("should parse and process markdown style in Println", func() { Expect(captureStdout(func() { _, _ = Println("This should be *bold*.") })).To(BeEquivalentTo("This should be \x1b[1mbold\x1b[0m.\n")) }) }) Context("process markdown style in Fprint functions", func() { var ( buf bytes.Buffer out *bufio.Writer ) BeforeEach(func() { SetColorSettings(ON, AUTO) buf = bytes.Buffer{} out = bufio.NewWriter(&buf) }) AfterEach(func() { buf.Reset() out = nil SetColorSettings(AUTO, AUTO) }) It("should parse and process markdown style in Fprint", func() { _, _ = Fprint(out, "This should be *bold*.") out.Flush() Expect(buf.String()).To(BeEquivalentTo("This should be \x1b[1mbold\x1b[0m.")) }) It("should parse and process markdown style in Fprintf", func() { _, _ = Fprintf(out, "This should be *%s*.", "bold") out.Flush() Expect(buf.String()).To(BeEquivalentTo("This should be \x1b[1mbold\x1b[0m.")) }) It("should parse and process markdown style in Fprintln", func() { _, _ = Fprintln(out, "This should be *bold*.") out.Flush() Expect(buf.String()).To(BeEquivalentTo("This should be \x1b[1mbold\x1b[0m.\n")) }) }) Context("process markdown style in Sprint functions", func() { BeforeEach(func() { SetColorSettings(ON, AUTO) }) AfterEach(func() { SetColorSettings(AUTO, AUTO) }) It("should parse and process markdown style in Sprint", func() { Expect(Sprint("This should be *bold*.")).To(BeEquivalentTo("This should be \x1b[1mbold\x1b[0m.")) }) It("should parse and process markdown style in Sprintf", func() { Expect(Sprintf("This should be *%s*.", "bold")).To(BeEquivalentTo("This should be \x1b[1mbold\x1b[0m.")) }) It("should parse and process markdown style in Sprintln", func() { Expect(Sprintln("This should be *bold*.")).To(BeEquivalentTo("This should be \x1b[1mbold\x1b[0m.\n")) }) }) Context("weird use cases and issues", func() { BeforeEach(func() { SetColorSettings(ON, AUTO) }) AfterEach(func() { SetColorSettings(AUTO, AUTO) }) It("should ignore escape sequences that cannot be processed", func() { Expect(Sprint("ok", "\x1b[38;2;1;2mnot ok\x1b[0m")).To( BeEquivalentTo("ok\x1b[38;2;1;2mnot ok\x1b[0m")) }) It("should not fail writing simple types", func() { Expect(Sprint(42)).To(Equal("42")) }) It("should not fail writing slices", func() { Expect(Sprint([]int{42, 1})).To(Equal("[42 1]")) }) }) }) bunt-1.3.4/render.go000066400000000000000000000115251422041533700143020ustar00rootroot00000000000000// Copyright © 2019 The Homeport Team // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. package bunt import ( "bytes" "fmt" "image/color" "math" "strconv" "strings" ciede2000 "github.com/mattn/go-ciede2000" ) func (s String) String() string { var ( buffer = &bytes.Buffer{} current = uint64(0) useColors = UseColors() ) for _, coloredRune := range s { if useColors && (current != coloredRune.Settings) { // In case text emphasis like bold, italic, or underline was set, // but is now turned off, a reset sequence is in order to ensure // that the text emphasis is removed. prepend := []uint8{} if isBitTurnedOff(current, coloredRune.Settings, boldMask) || isBitTurnedOff(current, coloredRune.Settings, italicMask) || isBitTurnedOff(current, coloredRune.Settings, underlineMask) { prepend = append(prepend, 0) } _, _ = buffer.WriteString(renderSGR(coloredRune.Settings, prepend...)) current = coloredRune.Settings } _, _ = buffer.WriteRune(coloredRune.Symbol) } // Make sure to finish with a reset escape sequence if current != 0 { _, _ = buffer.WriteString(renderSGR(0)) } return buffer.String() } func isBitTurnedOff(from uint64, to uint64, mask uint64) bool { return (from&mask) != 0 && (to&mask) == 0 } func renderSGR(setting uint64, prepend ...uint8) string { if setting == 0 { return renderEscapeSequence(0) } // init parameters with provided additional parameters to be prepended parameters := append([]uint8{}, prepend...) if (setting & boldMask) != 0 { parameters = append(parameters, 1) } if (setting & italicMask) != 0 { parameters = append(parameters, 3) } if (setting & underlineMask) != 0 { parameters = append(parameters, 4) } if (setting & fgMask) != 0 { r, g, b := uint8((setting>>8)&0xFF), uint8((setting>>16)&0xFF), uint8((setting>>24)&0xFF) if UseTrueColor() { parameters = append(parameters, 38, 2, r, g, b) } else { parameters = append(parameters, closest4bitColorParameter(r, g, b)) } } if (setting & bgMask) != 0 { r, g, b := uint8((setting>>32)&0xFF), uint8((setting>>40)&0xFF), uint8((setting>>48)&0xFF) if UseTrueColor() { parameters = append(parameters, 48, 2, r, g, b) } else { parameters = append(parameters, 10+closest4bitColorParameter(r, g, b)) } } return renderEscapeSequence(parameters...) } func renderEscapeSequence(a ...uint8) string { values := make([]string, len(a)) for i := range a { values[i] = strconv.Itoa(int(a[i])) } return fmt.Sprintf("\x1b[%sm", strings.Join(values, ";")) } // closest4bitColorParameter returns the color attribute which matches the best // with the provided RGB color func closest4bitColorParameter(r, g, b uint8) uint8 { var ( result = uint8(0) target = &color.RGBA{r, g, b, 0xFF} min = math.MaxFloat64 helperMap = map[uint8]*color.RGBA{ 30: {R: 0x00, G: 0x00, B: 0x00, A: 0xFF}, 31: {R: 0xAA, G: 0x00, B: 0x00, A: 0xFF}, 32: {R: 0x00, G: 0xAA, B: 0x00, A: 0xFF}, 33: {R: 0xFF, G: 0xFF, B: 0x00, A: 0xFF}, 34: {R: 0x00, G: 0x00, B: 0xAA, A: 0xFF}, 35: {R: 0xAA, G: 0x00, B: 0xAA, A: 0xFF}, 36: {R: 0x00, G: 0xAA, B: 0xAA, A: 0xFF}, 37: {R: 0xAA, G: 0xAA, B: 0xAA, A: 0xFF}, 90: {R: 0x55, G: 0x55, B: 0x55, A: 0xFF}, 91: {R: 0xFF, G: 0x55, B: 0x55, A: 0xFF}, 92: {R: 0x55, G: 0xFF, B: 0x55, A: 0xFF}, 93: {R: 0xFF, G: 0xFF, B: 0x55, A: 0xFF}, 94: {R: 0x55, G: 0x55, B: 0xFF, A: 0xFF}, 95: {R: 0xFF, G: 0x55, B: 0xFF, A: 0xFF}, 96: {R: 0x55, G: 0xFF, B: 0xFF, A: 0xFF}, 97: {R: 0xFF, G: 0xFF, B: 0xFF, A: 0xFF}, } ) // Calculate the distance between the target color and the available 4-bit // colors using the `deltaE` algorithm to find the best match. for attribute, candidate := range helperMap { if distance := ciede2000.Diff(target, candidate); distance < min { min, result = distance, attribute } } return result } bunt-1.3.4/render_test.go000066400000000000000000000045321422041533700153410ustar00rootroot00000000000000// Copyright © 2019 The Homeport Team // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. package bunt_test import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" . "github.com/gonvenience/bunt" ) var _ = Describe("render colored strings", func() { Context("verify that rendering of colored strings returns correct results", func() { It("should render colored output when colors are enabled", func() { SetColorSettings(ON, ON) defer SetColorSettings(AUTO, AUTO) input := "Example: \x1b[1mbold\x1b[0m, \x1b[3mitalic\x1b[0m, \x1b[4munderline\x1b[0m, \x1b[38;2;133;247;7mforeground\x1b[0m, and \x1b[48;2;133;247;7mbackground\x1b[0m." result, err := ParseString(input) Expect(err).ToNot(HaveOccurred()) Expect(result).ToNot(BeNil()) Expect(result.String()).To(BeEquivalentTo(input)) }) It("should render plain output when colors are not enabled", func() { SetColorSettings(OFF, OFF) defer SetColorSettings(AUTO, AUTO) input := "Example: \x1b[1mbold\x1b[0m, \x1b[3mitalic\x1b[0m, \x1b[4munderline\x1b[0m, \x1b[38;2;133;247;7mforeground\x1b[0m, and \x1b[48;2;133;247;7mbackground\x1b[0m." result, err := ParseString(input) Expect(err).ToNot(HaveOccurred()) Expect(result).ToNot(BeNil()) Expect(result.String()).To(BeEquivalentTo("Example: bold, italic, underline, foreground, and background.")) }) }) })