Skip to content

Commit

Permalink
fix(text) escape value before rendering styles (#20)
Browse files Browse the repository at this point in the history
* fix(text) escape value before rendering style

* fix(text) render value style before escaping

* test(text) add additional test cases
  • Loading branch information
tombell authored and aymanbagabas committed Feb 27, 2023
1 parent 462efd0 commit 761ad44
Show file tree
Hide file tree
Showing 2 changed files with 182 additions and 8 deletions.
26 changes: 18 additions & 8 deletions text.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ const (
indentSeparator = " │ "
)

func writeIndent(w io.Writer, str string, indent string, newline bool) {
func (l *logger) writeIndent(w io.Writer, str string, indent string, newline bool) {
// kindly borrowed from hclog
for {
nl := strings.IndexByte(str, '\n')
if nl == -1 {
if str != "" {
_, _ = w.Write([]byte(indent))
writeEscapedForOutput(w, str, false)
l.writeEscapedForOutput(w, str, false)
if newline {
_, _ = w.Write([]byte{'\n'})
}
Expand All @@ -32,7 +32,7 @@ func writeIndent(w io.Writer, str string, indent string, newline bool) {
}

_, _ = w.Write([]byte(indent))
writeEscapedForOutput(w, str[:nl], false)
l.writeEscapedForOutput(w, str[:nl], false)
_, _ = w.Write([]byte{'\n'})
str = str[nl+1:]
}
Expand All @@ -58,9 +58,12 @@ var bufPool = sync.Pool{
},
}

func writeEscapedForOutput(w io.Writer, str string, escapeQuotes bool) {
func (l *logger) writeEscapedForOutput(w io.Writer, str string, escapeQuotes bool) {
// kindly borrowed from hclog
if !needsEscaping(str) {
if !l.noStyles {
str = ValueStyle.Render(str)
}
_, _ = w.Write([]byte(str))
return
}
Expand Down Expand Up @@ -115,7 +118,12 @@ func writeEscapedForOutput(w io.Writer, str string, escapeQuotes bool) {
}
}

_, _ = w.Write(bb.Bytes())
s := bb.String()
if !l.noStyles {
s = ValueStyle.Render(s)
}

_, _ = w.Write([]byte(s))
}

// isNormal indicates if the rune is one allowed to exist as an unquoted
Expand Down Expand Up @@ -205,7 +213,6 @@ func (l *logger) textFormatter(keyvals ...interface{}) {
} else {
key = KeyStyle.Render(key)
}
val = ValueStyle.Render(val)
}

// Values may contain multiple lines, and that format
Expand All @@ -219,7 +226,7 @@ func (l *logger) textFormatter(keyvals ...interface{}) {
l.b.WriteString("\n ")
l.b.WriteString(key)
l.b.WriteString(sep + "\n")
writeIndent(&l.b, val, indentSep, moreKeys)
l.writeIndent(&l.b, val, indentSep, moreKeys)
// If there are more keyvals, separate them with a space.
if moreKeys {
l.b.WriteByte(' ')
Expand All @@ -229,9 +236,12 @@ func (l *logger) textFormatter(keyvals ...interface{}) {
l.b.WriteString(key)
l.b.WriteString(sep)
l.b.WriteByte('"')
writeEscapedForOutput(&l.b, val, true)
l.writeEscapedForOutput(&l.b, val, true)
l.b.WriteByte('"')
} else {
if !l.noStyles {
val = ValueStyle.Render(val)
}
l.b.WriteByte(' ')
l.b.WriteString(key)
l.b.WriteString(sep)
Expand Down
164 changes: 164 additions & 0 deletions text_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"testing"
"time"

"github.com/charmbracelet/lipgloss"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
Expand Down Expand Up @@ -228,3 +229,166 @@ func TestTextFatal(t *testing.T) {
}
t.Fatalf("process ran with err %v, want exit status 1", err)
}

func TestTextValueStyles(t *testing.T) {
var buf bytes.Buffer
logger := New(WithOutput(&buf)).(*logger)
logger.noStyles = false
ValueStyle = lipgloss.NewStyle().Bold(true)
cases := []struct {
name string
expected string
msg string
kvs []interface{}
f func(msg interface{}, kvs ...interface{})
}{
{
name: "simple message",
expected: fmt.Sprintf("%s info\n", InfoLevelStyle.Render("INFO")),
msg: "info",
kvs: nil,
f: logger.Info,
},
{
name: "ignored message",
expected: "",
msg: "this is a debug message",
kvs: nil,
f: logger.Debug,
},
{
name: "message with keyvals",
expected: fmt.Sprintf(
"%s info %s%s%s %s%s%s\n",
InfoLevelStyle.Render("INFO"),
KeyStyle.Render("key1"), SeparatorStyle.Render("="), ValueStyle.Render("val1"),
KeyStyle.Render("key2"), SeparatorStyle.Render("="), ValueStyle.Render("val2"),
),
msg: "info",
kvs: []interface{}{"key1", "val1", "key2", "val2"},
f: logger.Info,
},
{
name: "error message with multiline",
expected: fmt.Sprintf(
"%s info\n %s%s\n%s%s\n%s%s\n",
ErrorLevelStyle.Render("ERRO"),
KeyStyle.Render("key1"), SeparatorStyle.Render("="),
SeparatorStyle.Render(" │ "), ValueStyle.Render("val1"),
SeparatorStyle.Render(" │ "), ValueStyle.Render("val2"),
),
msg: "info",
kvs: []interface{}{"key1", "val1\nval2"},
f: logger.Error,
},
{
name: "error message with keyvals",
expected: fmt.Sprintf(
"%s info %s%s%s %s%s%s\n",
ErrorLevelStyle.Render("ERRO"),
KeyStyle.Render("key1"), SeparatorStyle.Render("="), ValueStyle.Render("val1"),
KeyStyle.Render("key2"), SeparatorStyle.Render("="), ValueStyle.Render("val2"),
),
msg: "info",
kvs: []interface{}{"key1", "val1", "key2", "val2"},
f: logger.Error,
},
{
name: "odd number of keyvals",
expected: fmt.Sprintf(
"%s info %s%s%s %s%s%s %s%s\"%s\"\n",
ErrorLevelStyle.Render("ERRO"),
KeyStyle.Render("key1"), SeparatorStyle.Render("="), ValueStyle.Render("val1"),
KeyStyle.Render("key2"), SeparatorStyle.Render("="), ValueStyle.Render("val2"),
KeyStyle.Render("key3"), SeparatorStyle.Render("="), ValueStyle.Render("missing value"),
),
msg: "info",
kvs: []interface{}{"key1", "val1", "key2", "val2", "key3"},
f: logger.Error,
},
{
name: "error field",
expected: fmt.Sprintf(
"%s info %s%s\"%s\"\n",
ErrorLevelStyle.Render("ERRO"),
KeyStyle.Render("key1"), SeparatorStyle.Render("="), ValueStyle.Render("error value"),
),
msg: "info",
kvs: []interface{}{"key1", errors.New("error value")},
f: logger.Error,
},
{
name: "struct field",
expected: fmt.Sprintf(
"%s info %s%s%s\n",
InfoLevelStyle.Render("INFO"),
KeyStyle.Render("key1"), SeparatorStyle.Render("="), ValueStyle.Render("{foo:bar}"),
),
msg: "info",
kvs: []interface{}{"key1", struct{ foo string }{foo: "bar"}},
f: logger.Info,
},
{
name: "struct field quoted",
expected: fmt.Sprintf(
"%s info %s%s\"%s\"\n",
InfoLevelStyle.Render("INFO"),
KeyStyle.Render("key1"), SeparatorStyle.Render("="), ValueStyle.Render("{foo:bar baz}"),
),
msg: "info",
kvs: []interface{}{"key1", struct{ foo string }{foo: "bar baz"}},
f: logger.Info,
},
{
name: "slice of strings",
expected: fmt.Sprintf(
"%s info %s%s\"%s\"\n",
ErrorLevelStyle.Render("ERRO"),
KeyStyle.Render("key1"), SeparatorStyle.Render("="), ValueStyle.Render("[foo bar]"),
),
msg: "info",
kvs: []interface{}{"key1", []string{"foo", "bar"}},
f: logger.Error,
},
{
name: "slice of structs",
expected: fmt.Sprintf(
"%s info %s%s\"%s\"\n",
ErrorLevelStyle.Render("ERRO"),
KeyStyle.Render("key1"), SeparatorStyle.Render("="), ValueStyle.Render("[{foo:bar} {foo:baz}]"),
),
msg: "info",
kvs: []interface{}{"key1", []struct{ foo string }{{foo: "bar"}, {foo: "baz"}}},
f: logger.Error,
},
{
name: "slice of errors",
expected: fmt.Sprintf(
"%s info %s%s\"%s\"\n",
ErrorLevelStyle.Render("ERRO"),
KeyStyle.Render("key1"), SeparatorStyle.Render("="), ValueStyle.Render("[error value1 error value2]"),
),
msg: "info",
kvs: []interface{}{"key1", []error{errors.New("error value1"), errors.New("error value2")}},
f: logger.Error,
},
{
name: "map of strings",
expected: fmt.Sprintf(
"%s info %s%s\"%s\"\n",
ErrorLevelStyle.Render("ERRO"),
KeyStyle.Render("key1"), SeparatorStyle.Render("="), ValueStyle.Render("map[baz:qux foo:bar]"),
),
msg: "info",
kvs: []interface{}{"key1", map[string]string{"foo": "bar", "baz": "qux"}},
f: logger.Error,
},
}
for _, c := range cases {
buf.Reset()
t.Run(c.name, func(t *testing.T) {
c.f(c.msg, c.kvs...)
assert.Equal(t, c.expected, buf.String())
})
}
}

0 comments on commit 761ad44

Please sign in to comment.