Skip to content

Commit

Permalink
feat: improve string escape (#19)
Browse files Browse the repository at this point in the history
  • Loading branch information
nrwiersma authored May 17, 2021
1 parent 349a3d8 commit 55c4a45
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 28 deletions.
4 changes: 4 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
run:
tests: false
timeout: 5m
linters-settings:
cyclop:
max-complexity: 12
skip-tests: true
linters:
enable-all: true
disable:
Expand Down
83 changes: 55 additions & 28 deletions format.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"strings"
"time"
"unicode/utf8"

"github.com/hamba/logger/v2/internal/bytes"
)
Expand Down Expand Up @@ -332,11 +333,13 @@ func (c *console) AppendInterface(buf *bytes.Buffer, v interface{}) {
c.AppendString(buf, fmt.Sprintf("%+v", v))
}

var noEscapeTable = [256]bool{}
const hex = "0123456789abcdef"

var noEscape = [256]bool{}

func init() {
for i := 0; i <= 0x7e; i++ {
noEscapeTable[i] = i >= 0x20 && i != '\\' && i != '"'
for i := 0x20; i <= 0x7e; i++ {
noEscape[i] = i != '\\' && i != '"'
}
}

Expand All @@ -345,41 +348,65 @@ func appendString(buf *bytes.Buffer, s string, quote bool) {
buf.WriteByte('"')
}

var needEscape bool
for i := 0; i < len(s); i++ {
if noEscapeTable[s[i]] {
start := 0
for i := 0; i < len(s); {
if noEscape[s[i]] {
i++
continue
}

if start < i {
buf.WriteString(s[start:i])
}
if tryAddASCII(buf, s[i]) {
i++
start = i
continue
}

r, size := utf8.DecodeRuneInString(s[i:])
if r == utf8.RuneError && size == 1 {
buf.WriteString(`\ufffd`)
i++
start = i
continue
}
needEscape = true
break
buf.WriteString(s[i : i+size])
i += size
start = i
}

if needEscape {
escapeString(buf, s)
} else {
buf.WriteString(s)
if start < len(s) {
if start == 0 {
buf.WriteString(s)
} else {
buf.WriteString(s[start:])
}
}

if quote {
buf.WriteByte('"')
}
}

func escapeString(buf *bytes.Buffer, s string) {
// TODO: improve this if the need arises.
for _, r := range s {
switch r {
case '\\', '"':
buf.WriteByte('\\')
buf.WriteRune(r)
case '\n':
buf.WriteString("\\n")
case '\r':
buf.WriteString("\\r")
case '\t':
buf.WriteString("\\t")
default:
buf.WriteRune(r)
}
func tryAddASCII(buf *bytes.Buffer, b byte) bool {
if b >= utf8.RuneSelf {
return false
}
switch b {
case '\\', '"':
buf.WriteByte('\\')
buf.WriteByte(b)
case '\n':
buf.WriteString("\\n")
case '\r':
buf.WriteString("\\r")
case '\t':
buf.WriteString("\\t")
default:
buf.WriteString(`\u00`)
buf.WriteByte(hex[b>>4])
buf.WriteByte(hex[b&0xF])
}
return true
}
15 changes: 15 additions & 0 deletions format_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ func TestJsonFormat_Strings(t *testing.T) {
in: string('\\'),
want: `"\\"`,
},
{
name: "special chars",
in: "some string with \"special ❤️ chars\" and somewhat realistic length",
want: `"some string with \"special ❤️ chars\" and somewhat realistic length"`,
},
}

for _, test := range tests {
Expand Down Expand Up @@ -213,6 +218,11 @@ func TestLogfmtFormat_Strings(t *testing.T) {
in: string('\\'),
want: `\\`,
},
{
name: "special chars",
in: "some string with \"special ❤️ chars\" and somewhat realistic length",
want: `"some string with \"special ❤️ chars\" and somewhat realistic length"`,
},
}

for _, test := range tests {
Expand Down Expand Up @@ -357,6 +367,11 @@ func TestConsoleFormat_Strings(t *testing.T) {
in: string('\\'),
want: `\\`,
},
{
name: "special chars",
in: "some string with \"special ❤️ chars\" and somewhat realistic length",
want: `some string with \"special ❤️ chars\" and somewhat realistic length`,
},
}

for _, test := range tests {
Expand Down

0 comments on commit 55c4a45

Please sign in to comment.