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) != `{
-  "hello": "world",
-  "what": 123,
-  "arr": ["1", "2", 1, 2, true, false, null],
-  "obj": {
-    "key1": null,
-    "ar\u001b[36mCyanr2": [1, 2, 3, "123", "456"]
-  }
-}
+	if string(res) != `{
+  "hello": "world",
+  "what": 123,
+  "arr": ["1", "2", 1, 2, true, false, null],
+  "obj": {
+    "key1": null,
+    "ar\u001b[36mCyanr2": [1, 2, 3, "123", "456"]
+  }
+}
 ` {
 		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

More details

Full run details