Skip to content

Commit

Permalink
Merge pull request #1 from soluble-ai/init
Browse files Browse the repository at this point in the history
initial code
  • Loading branch information
slshen authored Dec 4, 2019
2 parents 0ac5df3 + 8bc7501 commit ac58d53
Show file tree
Hide file tree
Showing 8 changed files with 309 additions and 1 deletion.
23 changes: 23 additions & 0 deletions .buildkite/pipeline.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Copyright 2019 Soluble Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

steps:
- label: ":rocket: Go"
command: "golangci-lint run -E gofmt -E stylecheck && go test -cover"
plugins:
- docker#v3.3.0:
image: gcr.io/soluble-ci/go-jnode-ci:latest
always-pull: true
shell: [ "/bin/bash", "-e", "-c" ]
propagate-uid-gid: true
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,18 @@
# go-colorize
# go-colorize

Go from this:

```go
Colorize("\n{primary:%s} eat {bg-success:pizza :pizza:}{bg-primary: and drink }{warning:beer} %s, but not {bg-danger:%s}\n\n",
"Most folks like to", ":beer:", "everyone")
```

To this:

![Screenshot](screenshot.png)

Works with https://github.com/kyokomi/emoji and https://github.com/fatih/color.

Default styles are:

![Default Styles](default_styles.png)
163 changes: 163 additions & 0 deletions colorize.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
// Copyright 2019 Soluble Inc
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/*
colorize does ANSI colored output and emoji substitution.
Colorize takes a fmt.Printf() template string and looks for styles
in the format:
{style-name:text}
And inserts the color esacpe sequences around the text. It then passes
the template off to emoji.Sprintf() from https://github.com/kyokomi/emoji.
It uses the color attribute names, color.Output, and color.NoColor from
https://github.com/fatih/color.
The style names are modeled off the basic bootstrap color names.
Example use:
colorize.Colorize("{primary:We} want {bg-success:beer :beer:} and {warning:pizza} :pizza:")
*/
package colorize

import (
"fmt"
"strconv"
"strings"

"github.com/fatih/color"
"github.com/kyokomi/emoji"
)

const escape = "\x1b"

// Style is a ANSI escape code sequence
type Style struct {
sequence string
enable *bool
}

// NewStyle creates a style from a set of attributes
func NewStyle(attributes ...color.Attribute) *Style {
codes := make([]string, len(attributes))
for i, attr := range attributes {
codes[i] = strconv.Itoa(int(attr))
}
sequence := fmt.Sprintf("%s[%sm", escape, strings.Join(codes, ";"))
return &Style{sequence, nil}
}

// Styles maps style names to styles
var Styles = map[string]*Style{
"primary": NewStyle(color.FgHiBlue),
"secondary": NewStyle(color.FgBlue),
"light": NewStyle(color.BgHiBlack),
"info": nil,
"success": NewStyle(color.FgGreen),
"warning": NewStyle(color.FgYellow),
"danger": NewStyle(color.FgHiRed),
"bg-primary": NewStyle(color.FgHiWhite, color.BgHiBlue),
"bg-secondary": NewStyle(color.FgHiWhite, color.BgBlue),
"bg-success": NewStyle(color.FgHiWhite, color.BgGreen),
"bg-warning": NewStyle(color.FgBlack, color.BgYellow),
"bg-danger": NewStyle(color.FgBlack, color.BgHiRed),
}
var reset = NewStyle(color.Reset)

// IsEnabled returns true if the style is enabled, either specifically
// to this style or globally via color.NoColor
func (style *Style) IsEnabled() bool {
if style == nil {
return false
}
if style.enable != nil {
return *style.enable
}
return !color.NoColor
}

// Enable or disable this style
func (style *Style) Enable(val bool) *Style {
style.enable = &val
return style
}

// Colorize some text, printing the output to color.Output
func Colorize(template string, values ...interface{}) {
fmt.Fprint(color.Output, SColorize(template, values...))
}

// Colorize some text, returning a string
func SColorize(template string, values ...interface{}) string {
var b strings.Builder
state := ' '
var nameStart int
var style *Style
var frag strings.Builder
for i, ch := range template {
switch state {
case ' ':
if ch == '{' {
nameStart = i
state = ch
} else {
b.WriteRune(ch)
if ch == '\\' {
state = ch
}
}
case '\\':
b.WriteRune(ch)
state = ' '
case '{':
if ch == ':' {
name := template[nameStart+1 : i]
style = Styles[name]
frag.Reset()
state = '}'
}
case '}':
if ch == '\\' {
state = ']'
} else if ch == '}' {
var enabled = style.IsEnabled()
if enabled {
b.WriteString(style.sequence)
}
b.WriteString(frag.String())
if enabled {
b.WriteString(reset.sequence)
}
state = ' '
} else {
frag.WriteRune(ch)
}
case ']':
frag.WriteRune(ch)
state = '}'

}
}
switch state {
case '{', '}', ']':
// if we found a '{' w/o completion, append everything from the start
b.WriteString(template[nameStart:])
}
template = b.String()
return emoji.Sprintf(template, values...)
}
81 changes: 81 additions & 0 deletions colorize_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Copyright 2019 Soluble Inc
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package colorize

import (
"fmt"
"github.com/fatih/color"
"sort"
"strings"
"testing"
)

func TestDemoDefaults(t *testing.T) {
t.SkipNow()
var template strings.Builder
params := make([]interface{}, 0, len(Styles))
names := make([]string, 0, len(Styles))
for k := range Styles {
names = append(names, k)
}
sort.Strings(names)
for _, k := range names {
template.WriteString(fmt.Sprintf("{%s: %%12s }\n", k))
params = append(params, k)
}
Colorize(template.String(), params...)
Colorize("\n{primary:%s} eat {bg-success:pizza :pizza:}{bg-primary: and drink }{warning:beer} %s, but not {bg-danger:%s}\n\n",
"Most folks like to", ":beer:", "everyone")
}

func TestBasic(t *testing.T) {
s := SColorize("{primary:%s} %s", "hello", "world")
if !strings.Contains(s, "hello") || !strings.Contains(s, "world") {
t.Errorf("string contents wrong")
}
Styles["primary"].Enable(false)
if Styles["primary"].IsEnabled() {
t.Error("primary is enabled")
}
if Styles["nope"].IsEnabled() {
t.Error("nope is enabled")
}
if SColorize("{primary:hello} world") != "hello world" {
t.Error("disable style didn't work")
}
color.Output = &strings.Builder{}
Colorize("hello, world")
if color.Output.(*strings.Builder).String() != "hello, world" {
t.Error("string didn't output")
}
}

func TestEscapes(t *testing.T) {
if e := SColorize("\\\n\r\t\v"); e != "\\\n\r\t\v" {
t.Errorf("escape sequences wrong: %s", e)
}
}

func TestIncomplete(t *testing.T) {
if s := SColorize("{primary:foo"); s != "{primary:foo" {
t.Error(s)
}
if s := SColorize("{nope:xxx\\}}"); s != "xxx}" {
t.Error(s)
}
if s := SColorize("{nope:foo\\ bar"); s != "{nope:foo\\ bar" {
t.Error(s)
}
}
Binary file added default_styles.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module github.com/soluble-ai/go-colorize

go 1.13

require (
github.com/fatih/color v1.7.0
github.com/kyokomi/emoji v2.1.0+incompatible
github.com/mattn/go-colorable v0.1.4 // indirect
github.com/mattn/go-isatty v0.0.10 // indirect
)
14 changes: 14 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/kyokomi/emoji v2.1.0+incompatible h1:+DYU2RgpI6OHG4oQkM5KlqD3Wd3UPEsX8jamTo1Mp6o=
github.com/kyokomi/emoji v2.1.0+incompatible/go.mod h1:mZ6aGCD7yk8j6QY6KICwnZ2pxoszVseX1DNoGtU2tBA=
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191008105621-543471e840be h1:QAcqgptGM8IQBC9K/RC4o+O9YmqEm0diQn9QmZw/0mU=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Binary file added screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit ac58d53

Please sign in to comment.