From 98245d998768283687cefca73c512fd60547d82e Mon Sep 17 00:00:00 2001 From: "Daniel G. Taylor" Date: Tue, 3 Oct 2023 11:02:08 -0700 Subject: [PATCH] fix: gron output with special chars --- cli/content.go | 2 +- cli/gron.go | 35 +++++++++++++---------------------- cli/gron_test.go | 9 +++++++++ 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/cli/content.go b/cli/content.go index 308aeb3..b132dd2 100644 --- a/cli/content.go +++ b/cli/content.go @@ -263,7 +263,7 @@ func (t Gron) Detect(contentType string) bool { // Marshal the value to a gron string. func (t Gron) Marshal(value interface{}) ([]byte, error) { - pb := NewPathBuffer([]byte("body"), 4) + pb := NewPathBuffer([][]byte{[]byte("body")}) out := make([]byte, 0, 1024) return marshalGron(pb, value, false, out) } diff --git a/cli/gron.go b/cli/gron.go index edc2b8a..505e6e8 100644 --- a/cli/gron.go +++ b/cli/gron.go @@ -12,41 +12,30 @@ import ( "unicode" ) -// PathBuffer is a low-allocation helper for building a path string like -// `foo.bar[2].baz`. It is not goroutine-safe, but the underlying buffer can -// be re-used within the same goroutine or via a `sync.Pool`. +// PathBuffer builds up a path string from multiple parts. type PathBuffer struct { - buf []byte - off int + parts [][]byte } func (b *PathBuffer) Push(s string) { - if b.off > 0 && s[0] != '[' { - b.buf = append(b.buf, '.') - b.off++ + if s[0] != '[' { + s = "." + s } - b.buf = append(b.buf, s...) - b.off += len(s) + b.parts = append(b.parts, []byte(s)) } func (b *PathBuffer) Pop() { - for b.off > 0 { - b.off-- - if b.buf[b.off] == '.' || b.buf[b.off] == '[' { - break - } - } - b.buf = b.buf[:b.off] + b.parts = b.parts[:len(b.parts)-1] } func (b *PathBuffer) Bytes() []byte { - return b.buf[:b.off] + return bytes.Join(b.parts, []byte{}) } -// NewPathBuffer creates a new path buffer with the given underlying byte slice -// and offset within that slice (for pre-loading with some path data). -func NewPathBuffer(buf []byte, offset int) *PathBuffer { - return &PathBuffer{buf: buf, off: offset} +// NewPathBuffer creates a new path buffer with the given underlying initial +// data loaded into it. +func NewPathBuffer(parts [][]byte) *PathBuffer { + return &PathBuffer{parts: parts} } // validFirstRune returns true for runes that are valid @@ -72,6 +61,8 @@ func identifier(s string) string { return s } + s = strings.ReplaceAll(s, `"`, `\"`) + return fmt.Sprintf(`["%s"]`, s) } diff --git a/cli/gron_test.go b/cli/gron_test.go index 508683c..e878e3b 100644 --- a/cli/gron_test.go +++ b/cli/gron_test.go @@ -29,6 +29,9 @@ func TestGronMarshal(t *testing.T) { {"e": "world & i <3 restish"}, {"f": []any{1, 2}, "g": time.Time{}, "h": []byte("foo")}, {"for": map[int]int{1: 2}}, + {"dotted.name": "foo"}, + {"name[with]brackets": "bar"}, + {"name\"with": "quote"}, }}, private: true, } @@ -52,6 +55,12 @@ body.d[1].h = "Zm9v"; body.d[2] = {}; body.d[2].for = {}; body.d[2].for["1"] = 2; +body.d[3] = {}; +body.d[3]["dotted.name"] = "foo"; +body.d[4] = {}; +body.d[4]["name[with]brackets"] = "bar"; +body.d[5] = {}; +body.d[5]["name\"with"] = "quote"; `, string(b)) // Invalid types should result in an error!