New Upstream Snapshot - golang-github-tidwall-pretty
Ready changes
Summary
Merged new upstream version: 1.2.1+ds (was: 1.0.5).
Resulting package
Built on 2022-10-21T09:15 (took 2m49s)
The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:
apt install -t fresh-snapshots golang-github-tidwall-pretty-dev
Lintian Result
Diff
diff --git a/README.md b/README.md
index 2dc5871..76c06a5 100644
--- a/README.md
+++ b/README.md
@@ -59,7 +59,7 @@ Will format the json to:
Color will colorize the json for outputing to the screen.
-```json
+```go
result = pretty.Color(json, nil)
```
@@ -79,7 +79,6 @@ Will format the json to:
{"name":{"first":"Tom","last":"Anderson"},"age":37,"children":["Sara","Alex","Jack"],"fav.movie":"Deer Hunter","friends":[{"first":"Janet","last":"Murphy","age":44}]}```
```
-
## Customized output
There's a `PrettyOptions(json, opts)` function which allows for customizing the output with the following options:
@@ -104,14 +103,15 @@ type Options struct {
Benchmarks of Pretty alongside the builtin `encoding/json` Indent/Compact methods.
```
-BenchmarkPretty-8 1000000 1283 ns/op 720 B/op 2 allocs/op
-BenchmarkUgly-8 3000000 426 ns/op 240 B/op 1 allocs/op
-BenchmarkUglyInPlace-8 5000000 340 ns/op 0 B/op 0 allocs/op
-BenchmarkJSONIndent-8 300000 4628 ns/op 1069 B/op 4 allocs/op
-BenchmarkJSONCompact-8 1000000 2469 ns/op 758 B/op 4 allocs/op
+BenchmarkPretty-16 1000000 1034 ns/op 720 B/op 2 allocs/op
+BenchmarkPrettySortKeys-16 586797 1983 ns/op 2848 B/op 14 allocs/op
+BenchmarkUgly-16 4652365 254 ns/op 240 B/op 1 allocs/op
+BenchmarkUglyInPlace-16 6481233 183 ns/op 0 B/op 0 allocs/op
+BenchmarkJSONIndent-16 450654 2687 ns/op 1221 B/op 0 allocs/op
+BenchmarkJSONCompact-16 685111 1699 ns/op 442 B/op 0 allocs/op
```
-*These benchmarks were run on a MacBook Pro 15" 2.8 GHz Intel Core i7 using Go 1.7.*
+*These benchmarks were run on a MacBook Pro 2.4 GHz 8-Core Intel Core i9.*
## Contact
Josh Baker [@tidwall](http://twitter.com/tidwall)
diff --git a/debian/changelog b/debian/changelog
index c16bbe5..c0c8b41 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+golang-github-tidwall-pretty (1.2.1+ds-1) UNRELEASED; urgency=low
+
+ * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk> Fri, 21 Oct 2022 09:13:08 -0000
+
golang-github-tidwall-pretty (1.0.5-1) unstable; urgency=medium
* New upstream release.
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..6106735
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,3 @@
+module github.com/tidwall/pretty
+
+go 1.16
diff --git a/pretty.go b/pretty.go
index 92a68dd..d705f9c 100644
--- a/pretty.go
+++ b/pretty.go
@@ -1,7 +1,10 @@
package pretty
import (
+ "bytes"
+ "encoding/json"
"sort"
+ "strconv"
)
// Options is Pretty options
@@ -84,6 +87,14 @@ func ugly(dst, src []byte) []byte {
return dst
}
+func isNaNOrInf(src []byte) bool {
+ return src[0] == 'i' || //Inf
+ src[0] == 'I' || // inf
+ src[0] == '+' || // +Inf
+ src[0] == 'N' || // Nan
+ (src[0] == 'n' && len(src) > 1 && src[1] != 'u') // nan
+}
+
func appendPrettyAny(buf, json []byte, i int, pretty bool, width int, prefix, indent string, sortkeys bool, tabs, nl, max int) ([]byte, int, int, bool) {
for ; i < len(json); i++ {
if json[i] <= ' ' {
@@ -92,7 +103,8 @@ func appendPrettyAny(buf, json []byte, i int, pretty bool, width int, prefix, in
if json[i] == '"' {
return appendPrettyString(buf, json, i, nl)
}
- if (json[i] >= '0' && json[i] <= '9') || json[i] == '-' {
+
+ if (json[i] >= '0' && json[i] <= '9') || json[i] == '-' || isNaNOrInf(json[i:]) {
return appendPrettyNumber(buf, json, i, nl)
}
if json[i] == '{' {
@@ -121,6 +133,7 @@ type pair struct {
type byKeyVal struct {
sorted bool
json []byte
+ buf []byte
pairs []pair
}
@@ -128,21 +141,110 @@ func (arr *byKeyVal) Len() int {
return len(arr.pairs)
}
func (arr *byKeyVal) Less(i, j int) bool {
- key1 := arr.json[arr.pairs[i].kstart+1 : arr.pairs[i].kend-1]
- key2 := arr.json[arr.pairs[j].kstart+1 : arr.pairs[j].kend-1]
- if string(key1) < string(key2) {
+ if arr.isLess(i, j, byKey) {
return true
}
- if string(key1) > string(key2) {
+ if arr.isLess(j, i, byKey) {
return false
}
- return arr.pairs[i].vstart < arr.pairs[j].vstart
+ return arr.isLess(i, j, byVal)
}
func (arr *byKeyVal) Swap(i, j int) {
arr.pairs[i], arr.pairs[j] = arr.pairs[j], arr.pairs[i]
arr.sorted = true
}
+type byKind int
+
+const (
+ byKey byKind = 0
+ byVal byKind = 1
+)
+
+type jtype int
+
+const (
+ jnull jtype = iota
+ jfalse
+ jnumber
+ jstring
+ jtrue
+ jjson
+)
+
+func getjtype(v []byte) jtype {
+ if len(v) == 0 {
+ return jnull
+ }
+ switch v[0] {
+ case '"':
+ return jstring
+ case 'f':
+ return jfalse
+ case 't':
+ return jtrue
+ case 'n':
+ return jnull
+ case '[', '{':
+ return jjson
+ default:
+ return jnumber
+ }
+}
+
+func (arr *byKeyVal) isLess(i, j int, kind byKind) bool {
+ k1 := arr.json[arr.pairs[i].kstart:arr.pairs[i].kend]
+ k2 := arr.json[arr.pairs[j].kstart:arr.pairs[j].kend]
+ var v1, v2 []byte
+ if kind == byKey {
+ v1 = k1
+ v2 = k2
+ } else {
+ v1 = bytes.TrimSpace(arr.buf[arr.pairs[i].vstart:arr.pairs[i].vend])
+ v2 = bytes.TrimSpace(arr.buf[arr.pairs[j].vstart:arr.pairs[j].vend])
+ if len(v1) >= len(k1)+1 {
+ v1 = bytes.TrimSpace(v1[len(k1)+1:])
+ }
+ if len(v2) >= len(k2)+1 {
+ v2 = bytes.TrimSpace(v2[len(k2)+1:])
+ }
+ }
+ t1 := getjtype(v1)
+ t2 := getjtype(v2)
+ if t1 < t2 {
+ return true
+ }
+ if t1 > t2 {
+ return false
+ }
+ if t1 == jstring {
+ s1 := parsestr(v1)
+ s2 := parsestr(v2)
+ return string(s1) < string(s2)
+ }
+ if t1 == jnumber {
+ n1, _ := strconv.ParseFloat(string(v1), 64)
+ n2, _ := strconv.ParseFloat(string(v2), 64)
+ return n1 < n2
+ }
+ return string(v1) < string(v2)
+
+}
+
+func parsestr(s []byte) []byte {
+ for i := 1; i < len(s); i++ {
+ if s[i] == '\\' {
+ var str string
+ json.Unmarshal(s, &str)
+ return []byte(str)
+ }
+ if s[i] == '"' {
+ return s[1:i]
+ }
+ }
+ return nil
+}
+
func appendPrettyObject(buf, json []byte, i int, open, close byte, pretty bool, width int, prefix, indent string, sortkeys bool, tabs, nl, max int) ([]byte, int, int, bool) {
var ok bool
if width > 0 {
@@ -249,7 +351,7 @@ func sortPairs(json, buf []byte, pairs []pair) []byte {
}
vstart := pairs[0].vstart
vend := pairs[len(pairs)-1].vend
- arr := byKeyVal{false, json, pairs}
+ arr := byKeyVal{false, json, buf, pairs}
sort.Stable(&arr)
if !arr.sorted {
return buf
@@ -320,6 +422,7 @@ type Style struct {
Key, String, Number [2]string
True, False, Null [2]string
Escape [2]string
+ Brackets [2]string
Append func(dst []byte, c byte) []byte
}
@@ -337,13 +440,14 @@ var TerminalStyle *Style
func init() {
TerminalStyle = &Style{
- Key: [2]string{"\x1B[94m", "\x1B[0m"},
- String: [2]string{"\x1B[92m", "\x1B[0m"},
- Number: [2]string{"\x1B[93m", "\x1B[0m"},
- True: [2]string{"\x1B[96m", "\x1B[0m"},
- False: [2]string{"\x1B[96m", "\x1B[0m"},
- Null: [2]string{"\x1B[91m", "\x1B[0m"},
- Escape: [2]string{"\x1B[35m", "\x1B[0m"},
+ Key: [2]string{"\x1B[1m\x1B[94m", "\x1B[0m"},
+ String: [2]string{"\x1B[32m", "\x1B[0m"},
+ Number: [2]string{"\x1B[33m", "\x1B[0m"},
+ True: [2]string{"\x1B[36m", "\x1B[0m"},
+ False: [2]string{"\x1B[36m", "\x1B[0m"},
+ Null: [2]string{"\x1B[2m", "\x1B[0m"},
+ Escape: [2]string{"\x1B[35m", "\x1B[0m"},
+ Brackets: [2]string{"\x1B[1m", "\x1B[0m"},
Append: func(dst []byte, c byte) []byte {
if c < ' ' && (c != '\r' && c != '\n' && c != '\t' && c != '\v') {
dst = append(dst, "\\u00"...)
@@ -437,16 +541,22 @@ func Color(src []byte, style *Style) []byte {
}
} else if src[i] == '{' || src[i] == '[' {
stack = append(stack, stackt{src[i], src[i] == '{'})
+ dst = append(dst, style.Brackets[0]...)
dst = apnd(dst, src[i])
+ dst = append(dst, style.Brackets[1]...)
} else if (src[i] == '}' || src[i] == ']') && len(stack) > 0 {
stack = stack[:len(stack)-1]
+ dst = append(dst, style.Brackets[0]...)
dst = apnd(dst, src[i])
+ dst = append(dst, style.Brackets[1]...)
} else if (src[i] == ':' || src[i] == ',') && len(stack) > 0 && stack[len(stack)-1].kind == '{' {
stack[len(stack)-1].key = !stack[len(stack)-1].key
+ dst = append(dst, style.Brackets[0]...)
dst = apnd(dst, src[i])
+ dst = append(dst, style.Brackets[1]...)
} else {
var kind byte
- if (src[i] >= '0' && src[i] <= '9') || src[i] == '-' {
+ if (src[i] >= '0' && src[i] <= '9') || src[i] == '-' || isNaNOrInf(src[i:]) {
kind = '0'
dst = append(dst, style.Number[0]...)
} else if src[i] == 't' {
@@ -483,3 +593,90 @@ func Color(src []byte, style *Style) []byte {
}
return dst
}
+
+// Spec strips out comments and trailing commas and convert the input to a
+// valid JSON per the official spec: https://tools.ietf.org/html/rfc8259
+//
+// The resulting JSON will always be the same length as the input and it will
+// include all of the same line breaks at matching offsets. This is to ensure
+// the result can be later processed by a external parser and that that
+// parser will report messages or errors with the correct offsets.
+func Spec(src []byte) []byte {
+ return spec(src, nil)
+}
+
+// SpecInPlace is the same as Spec, but this method reuses the input json
+// buffer to avoid allocations. Do not use the original bytes slice upon return.
+func SpecInPlace(src []byte) []byte {
+ return spec(src, src)
+}
+
+func spec(src, dst []byte) []byte {
+ dst = dst[:0]
+ for i := 0; i < len(src); i++ {
+ if src[i] == '/' {
+ if i < len(src)-1 {
+ if src[i+1] == '/' {
+ dst = append(dst, ' ', ' ')
+ i += 2
+ for ; i < len(src); i++ {
+ if src[i] == '\n' {
+ dst = append(dst, '\n')
+ break
+ } else if src[i] == '\t' || src[i] == '\r' {
+ dst = append(dst, src[i])
+ } else {
+ dst = append(dst, ' ')
+ }
+ }
+ continue
+ }
+ if src[i+1] == '*' {
+ dst = append(dst, ' ', ' ')
+ i += 2
+ for ; i < len(src)-1; i++ {
+ if src[i] == '*' && src[i+1] == '/' {
+ dst = append(dst, ' ', ' ')
+ i++
+ break
+ } else if src[i] == '\n' || src[i] == '\t' ||
+ src[i] == '\r' {
+ dst = append(dst, src[i])
+ } else {
+ dst = append(dst, ' ')
+ }
+ }
+ continue
+ }
+ }
+ }
+ dst = append(dst, src[i])
+ if src[i] == '"' {
+ for i = i + 1; i < len(src); i++ {
+ dst = append(dst, src[i])
+ if src[i] == '"' {
+ j := i - 1
+ for ; ; j-- {
+ if src[j] != '\\' {
+ break
+ }
+ }
+ if (j-i)%2 != 0 {
+ break
+ }
+ }
+ }
+ } else if src[i] == '}' || src[i] == ']' {
+ for j := len(dst) - 2; j >= 0; j-- {
+ if dst[j] <= ' ' {
+ continue
+ }
+ if dst[j] == ',' {
+ dst[j] = ' '
+ }
+ break
+ }
+ }
+ }
+ return dst
+}
diff --git a/pretty_test.go b/pretty_test.go
index c232874..f7cc898 100644
--- a/pretty_test.go
+++ b/pretty_test.go
@@ -373,15 +373,15 @@ func TestColor(t *testing.T) {
"arr":["1","2",1,2,true,false,null],
"obj":{"key1":null,"ar`+"\x1B[36m"+`Cyanr2":[1,2,3,"123","456"]}}
`)), nil)
- if string(res) != `{
- [94m"hello"[0m: [92m"world"[0m,
- [94m"what"[0m: [93m123[0m,
- [94m"arr"[0m: [[92m"1"[0m, [92m"2"[0m, [93m1[0m, [93m2[0m, [96mtrue[0m, [96mfalse[0m, [91mnull[0m],
- [94m"obj"[0m: {
- [94m"key1"[0m: [91mnull[0m,
- [94m"ar\u001b[36mCyanr2"[0m: [[93m1[0m, [93m2[0m, [93m3[0m, [92m"123"[0m, [92m"456"[0m]
- }
-}
+ if string(res) != `[1m{[0m
+ [1m[94m"hello"[0m[1m:[0m [32m"world"[0m[1m,[0m
+ [1m[94m"what"[0m[1m:[0m [33m123[0m[1m,[0m
+ [1m[94m"arr"[0m[1m:[0m [1m[[0m[32m"1"[0m, [32m"2"[0m, [33m1[0m, [33m2[0m, [36mtrue[0m, [36mfalse[0m, [2mnull[0m[1m][0m[1m,[0m
+ [1m[94m"obj"[0m[1m:[0m [1m{[0m
+ [1m[94m"key1"[0m[1m:[0m [2mnull[0m[1m,[0m
+ [1m[94m"ar\u001b[36mCyanr2"[0m[1m:[0m [1m[[0m[33m1[0m, [33m2[0m, [33m3[0m, [32m"123"[0m, [32m"456"[0m[1m][0m
+ [1m}[0m
+[1m}[0m
` {
t.Fatal("invalid output")
}
@@ -420,6 +420,47 @@ func BenchmarkUglyInPlace(t *testing.B) {
UglyInPlace(example2)
}
}
+
+var example3 = []byte(`
+{
+ /* COMMENT 1 */
+ "name": {
+ "last": "Sanders", // outer 1
+ "first": "Janet", // outer 2
+ },
+ // COMMENT 2
+ "children": [
+ "Andy", "Carol", "Mike", // outer 3
+ ],
+ /*
+ COMMENT 3
+ */
+ "values": [
+ 10.10, true, false, null, "hello", {},
+ ],
+ "values2": {},
+ "values3": [],
+ "deep": {"deep":{"deep":[1,2,3,4,5,],}}
+}
+`)
+
+func BenchmarkSpec(t *testing.B) {
+ t.ReportAllocs()
+ t.ResetTimer()
+ for i := 0; i < t.N; i++ {
+ Ugly(example3)
+ }
+}
+
+func BenchmarkSpecInPlace(t *testing.B) {
+ example4 := []byte(string(example3))
+ t.ReportAllocs()
+ t.ResetTimer()
+ for i := 0; i < t.N; i++ {
+ UglyInPlace(example4)
+ }
+}
+
func BenchmarkJSONIndent(t *testing.B) {
var dst bytes.Buffer
t.ReportAllocs()
@@ -451,7 +492,7 @@ func TestPrettyStableSort(t *testing.T) {
opts := *DefaultOptions
opts.SortKeys = true
json = string(Ugly(PrettyOptions([]byte(json), &opts)))
- if json != `{"a":3,"a":2,"a":1,"b":3,"b":2,"b":1,"c":3,"c":2,"c":1}` {
+ if json != `{"a":1,"a":2,"a":3,"b":1,"b":2,"b":3,"c":1,"c":2,"c":3}` {
t.Fatal("out of order")
}
}
@@ -469,3 +510,62 @@ func TestPrettyColor(t *testing.T) {
t.Fatalf("expected '%s', got '%s'", exp, ret)
}
}
+
+func TestSpec(t *testing.T) {
+ json := `
+ { // hello
+ "c": 3,"b":3, // jello
+ /* SOME
+ LIKE
+ IT
+ HAUT */
+ "d": [ 1, /* 2 */ 3, 4, ],
+ }`
+ expect := `
+ {
+ "c": 3,"b":3,
+
+
+
+
+ "d": [ 1, 3, 4 ]
+ }`
+ out := string(Spec([]byte(json)))
+ if out != expect {
+ t.Fatalf("expected '%s', got '%s'", expect, out)
+ }
+ out = string(SpecInPlace([]byte(json)))
+ if out != expect {
+ t.Fatalf("expected '%s', got '%s'", expect, out)
+ }
+}
+
+func TestStableSort10(t *testing.T) {
+ expect := `{"key":"abc","key":"bbb","key":"rrr","key":"value","key3":3}`
+ jsons := []string{
+ `{"key3":3,"key":"abc","key":"value","key":"rrr","key":"bbb"}`,
+ `{"key":"abc","key":"bbb","key":"value","key3":3,"key":"rrr"}`,
+ `{"key":"bbb","key":"value","key":"rrr","key3":3,"key":"abc"}`,
+ `{"key3":3,"key":"abc","key":"bbb","key":"value","key":"rrr"}`,
+ `{"key3":3,"key":"abc","key":"bbb","key":"value","key":"rrr"}`,
+ }
+ opts := *DefaultOptions
+ opts.SortKeys = true
+ for _, json := range jsons {
+ json = string(Ugly(PrettyOptions([]byte(json), &opts)))
+ if json != expect {
+ t.Fatalf("expected '%s', got '%s'", expect, json)
+ }
+ }
+}
+
+func TestNaN(t *testing.T) {
+ vals := []string{"NaN", "nan", "Nan", "nAn", "inf", "Inf", "-inf", "+Inf"}
+ for _, val := range vals {
+ json := `{"num":` + val + `}`
+ res := string(Ugly(Pretty([]byte(json))))
+ if res != json {
+ t.Fatalf("expected '%s', got '%s'", json, res)
+ }
+ }
+}
Debdiff
[The following lists of changes regard files as different if they have different names, permissions or owners.]
Files in second set of .debs but not in first
-rw-r--r-- root/root /usr/share/gocode/src/github.com/tidwall/pretty/go.mod
No differences were encountered in the control files