
The big change is that the GeoJSON package has been completely rewritten to fix a few of geometry calculation bugs, increase performance, and to better follow the GeoJSON spec RFC 7946. GeoJSON updates - A LineString now requires at least two points. - All json members, even foreign, now persist with the object. - The bbox member persists too but is no longer used for geometry calculations. This is change in behavior. Previously Tile38 would treat the bbox as the object's physical rectangle. - Corrections to geometry intersects and within calculations. Faster spatial queries - The performance of Point-in-polygon and object intersect operations are greatly improved for complex polygons and line strings. It went from O(n) to roughly O(log n). - The same for all collection types with many children, including FeatureCollection, GeometryCollection, MultiPoint, MultiLineString, and MultiPolygon. Codebase changes - The pkg directory has been renamed to internal - The GeoJSON internal package has been moved to a seperate repo at https://github.com/tidwall/geojson. It's now vendored. Please look out for higher memory usage for datasets using complex shapes. A complex shape is one that has 64 or more points. For these shapes it's expected that there will be increase of least 54 bytes per point.
433 lines
10 KiB
Go
433 lines
10 KiB
Go
package pretty
|
|
|
|
import (
|
|
"sort"
|
|
)
|
|
|
|
// Options is Pretty options
|
|
type Options struct {
|
|
// Width is an max column width for single line arrays
|
|
// Default is 80
|
|
Width int
|
|
// Prefix is a prefix for all lines
|
|
// Default is an empty string
|
|
Prefix string
|
|
// Indent is the nested indentation
|
|
// Default is two spaces
|
|
Indent string
|
|
// SortKeys will sort the keys alphabetically
|
|
// Default is false
|
|
SortKeys bool
|
|
}
|
|
|
|
// DefaultOptions is the default options for pretty formats.
|
|
var DefaultOptions = &Options{Width: 80, Prefix: "", Indent: " ", SortKeys: false}
|
|
|
|
// Pretty converts the input json into a more human readable format where each
|
|
// element is on it's own line with clear indentation.
|
|
func Pretty(json []byte) []byte { return PrettyOptions(json, nil) }
|
|
|
|
// PrettyOptions is like Pretty but with customized options.
|
|
func PrettyOptions(json []byte, opts *Options) []byte {
|
|
if opts == nil {
|
|
opts = DefaultOptions
|
|
}
|
|
buf := make([]byte, 0, len(json))
|
|
if len(opts.Prefix) != 0 {
|
|
buf = append(buf, opts.Prefix...)
|
|
}
|
|
buf, _, _, _ = appendPrettyAny(buf, json, 0, true,
|
|
opts.Width, opts.Prefix, opts.Indent, opts.SortKeys,
|
|
0, 0, -1)
|
|
if len(buf) > 0 {
|
|
buf = append(buf, '\n')
|
|
}
|
|
return buf
|
|
}
|
|
|
|
// Ugly removes insignificant space characters from the input json byte slice
|
|
// and returns the compacted result.
|
|
func Ugly(json []byte) []byte {
|
|
buf := make([]byte, 0, len(json))
|
|
return ugly(buf, json)
|
|
}
|
|
|
|
// UglyInPlace removes insignificant space characters from the input json
|
|
// byte slice and returns the compacted result. This method reuses the
|
|
// input json buffer to avoid allocations. Do not use the original bytes
|
|
// slice upon return.
|
|
func UglyInPlace(json []byte) []byte { return ugly(json, json) }
|
|
|
|
func ugly(dst, src []byte) []byte {
|
|
dst = dst[:0]
|
|
for i := 0; i < len(src); i++ {
|
|
if src[i] > ' ' {
|
|
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
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return dst
|
|
}
|
|
|
|
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] <= ' ' {
|
|
continue
|
|
}
|
|
if json[i] == '"' {
|
|
return appendPrettyString(buf, json, i, nl)
|
|
}
|
|
if (json[i] >= '0' && json[i] <= '9') || json[i] == '-' {
|
|
return appendPrettyNumber(buf, json, i, nl)
|
|
}
|
|
if json[i] == '{' {
|
|
return appendPrettyObject(buf, json, i, '{', '}', pretty, width, prefix, indent, sortkeys, tabs, nl, max)
|
|
}
|
|
if json[i] == '[' {
|
|
return appendPrettyObject(buf, json, i, '[', ']', pretty, width, prefix, indent, sortkeys, tabs, nl, max)
|
|
}
|
|
switch json[i] {
|
|
case 't':
|
|
return append(buf, 't', 'r', 'u', 'e'), i + 4, nl, true
|
|
case 'f':
|
|
return append(buf, 'f', 'a', 'l', 's', 'e'), i + 5, nl, true
|
|
case 'n':
|
|
return append(buf, 'n', 'u', 'l', 'l'), i + 4, nl, true
|
|
}
|
|
}
|
|
return buf, i, nl, true
|
|
}
|
|
|
|
type pair struct {
|
|
kstart, kend int
|
|
vstart, vend int
|
|
}
|
|
|
|
type byKey struct {
|
|
sorted bool
|
|
json []byte
|
|
pairs []pair
|
|
}
|
|
|
|
func (arr *byKey) Len() int {
|
|
return len(arr.pairs)
|
|
}
|
|
func (arr *byKey) 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]
|
|
return string(key1) < string(key2)
|
|
}
|
|
func (arr *byKey) Swap(i, j int) {
|
|
arr.pairs[i], arr.pairs[j] = arr.pairs[j], arr.pairs[i]
|
|
arr.sorted = true
|
|
}
|
|
|
|
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 {
|
|
if pretty && open == '[' && max == -1 {
|
|
// here we try to create a single line array
|
|
max := width - (len(buf) - nl)
|
|
if max > 3 {
|
|
s1, s2 := len(buf), i
|
|
buf, i, _, ok = appendPrettyObject(buf, json, i, '[', ']', false, width, prefix, "", sortkeys, 0, 0, max)
|
|
if ok && len(buf)-s1 <= max {
|
|
return buf, i, nl, true
|
|
}
|
|
buf = buf[:s1]
|
|
i = s2
|
|
}
|
|
} else if max != -1 && open == '{' {
|
|
return buf, i, nl, false
|
|
}
|
|
}
|
|
buf = append(buf, open)
|
|
i++
|
|
var pairs []pair
|
|
if open == '{' && sortkeys {
|
|
pairs = make([]pair, 0, 8)
|
|
}
|
|
var n int
|
|
for ; i < len(json); i++ {
|
|
if json[i] <= ' ' {
|
|
continue
|
|
}
|
|
if json[i] == close {
|
|
if pretty {
|
|
if open == '{' && sortkeys {
|
|
buf = sortPairs(json, buf, pairs)
|
|
}
|
|
if n > 0 {
|
|
nl = len(buf)
|
|
buf = append(buf, '\n')
|
|
}
|
|
if buf[len(buf)-1] != open {
|
|
buf = appendTabs(buf, prefix, indent, tabs)
|
|
}
|
|
}
|
|
buf = append(buf, close)
|
|
return buf, i + 1, nl, open != '{'
|
|
}
|
|
if open == '[' || json[i] == '"' {
|
|
if n > 0 {
|
|
buf = append(buf, ',')
|
|
if width != -1 {
|
|
buf = append(buf, ' ')
|
|
}
|
|
}
|
|
var p pair
|
|
if pretty {
|
|
nl = len(buf)
|
|
buf = append(buf, '\n')
|
|
if open == '{' && sortkeys {
|
|
p.kstart = i
|
|
p.vstart = len(buf)
|
|
}
|
|
buf = appendTabs(buf, prefix, indent, tabs+1)
|
|
}
|
|
if open == '{' {
|
|
buf, i, nl, _ = appendPrettyString(buf, json, i, nl)
|
|
if sortkeys {
|
|
p.kend = i
|
|
}
|
|
buf = append(buf, ':')
|
|
if pretty {
|
|
buf = append(buf, ' ')
|
|
}
|
|
}
|
|
buf, i, nl, ok = appendPrettyAny(buf, json, i, pretty, width, prefix, indent, sortkeys, tabs+1, nl, max)
|
|
if max != -1 && !ok {
|
|
return buf, i, nl, false
|
|
}
|
|
if pretty && open == '{' && sortkeys {
|
|
p.vend = len(buf)
|
|
if p.kstart > p.kend || p.vstart > p.vend {
|
|
// bad data. disable sorting
|
|
sortkeys = false
|
|
} else {
|
|
pairs = append(pairs, p)
|
|
}
|
|
}
|
|
i--
|
|
n++
|
|
}
|
|
}
|
|
return buf, i, nl, open != '{'
|
|
}
|
|
func sortPairs(json, buf []byte, pairs []pair) []byte {
|
|
if len(pairs) == 0 {
|
|
return buf
|
|
}
|
|
vstart := pairs[0].vstart
|
|
vend := pairs[len(pairs)-1].vend
|
|
arr := byKey{false, json, pairs}
|
|
sort.Sort(&arr)
|
|
if !arr.sorted {
|
|
return buf
|
|
}
|
|
nbuf := make([]byte, 0, vend-vstart)
|
|
for i, p := range pairs {
|
|
nbuf = append(nbuf, buf[p.vstart:p.vend]...)
|
|
if i < len(pairs)-1 {
|
|
nbuf = append(nbuf, ',')
|
|
nbuf = append(nbuf, '\n')
|
|
}
|
|
}
|
|
return append(buf[:vstart], nbuf...)
|
|
}
|
|
|
|
func appendPrettyString(buf, json []byte, i, nl int) ([]byte, int, int, bool) {
|
|
s := i
|
|
i++
|
|
for ; i < len(json); i++ {
|
|
if json[i] == '"' {
|
|
var sc int
|
|
for j := i - 1; j > s; j-- {
|
|
if json[j] == '\\' {
|
|
sc++
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
if sc%2 == 1 {
|
|
continue
|
|
}
|
|
i++
|
|
break
|
|
}
|
|
}
|
|
return append(buf, json[s:i]...), i, nl, true
|
|
}
|
|
|
|
func appendPrettyNumber(buf, json []byte, i, nl int) ([]byte, int, int, bool) {
|
|
s := i
|
|
i++
|
|
for ; i < len(json); i++ {
|
|
if json[i] <= ' ' || json[i] == ',' || json[i] == ':' || json[i] == ']' || json[i] == '}' {
|
|
break
|
|
}
|
|
}
|
|
return append(buf, json[s:i]...), i, nl, true
|
|
}
|
|
|
|
func appendTabs(buf []byte, prefix, indent string, tabs int) []byte {
|
|
if len(prefix) != 0 {
|
|
buf = append(buf, prefix...)
|
|
}
|
|
if len(indent) == 2 && indent[0] == ' ' && indent[1] == ' ' {
|
|
for i := 0; i < tabs; i++ {
|
|
buf = append(buf, ' ', ' ')
|
|
}
|
|
} else {
|
|
for i := 0; i < tabs; i++ {
|
|
buf = append(buf, indent...)
|
|
}
|
|
}
|
|
return buf
|
|
}
|
|
|
|
// Style is the color style
|
|
type Style struct {
|
|
Key, String, Number [2]string
|
|
True, False, Null [2]string
|
|
Append func(dst []byte, c byte) []byte
|
|
}
|
|
|
|
func hexp(p byte) byte {
|
|
switch {
|
|
case p < 10:
|
|
return p + '0'
|
|
default:
|
|
return (p - 10) + 'a'
|
|
}
|
|
}
|
|
|
|
// TerminalStyle is for terminals
|
|
var 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"},
|
|
Append: func(dst []byte, c byte) []byte {
|
|
if c < ' ' && (c != '\r' && c != '\n' && c != '\t' && c != '\v') {
|
|
dst = append(dst, "\\u00"...)
|
|
dst = append(dst, hexp((c>>4)&0xF))
|
|
return append(dst, hexp((c)&0xF))
|
|
}
|
|
return append(dst, c)
|
|
},
|
|
}
|
|
|
|
// Color will colorize the json. The style parma is used for customizing
|
|
// the colors. Passing nil to the style param will use the default
|
|
// TerminalStyle.
|
|
func Color(src []byte, style *Style) []byte {
|
|
if style == nil {
|
|
style = TerminalStyle
|
|
}
|
|
apnd := style.Append
|
|
if apnd == nil {
|
|
apnd = func(dst []byte, c byte) []byte {
|
|
return append(dst, c)
|
|
}
|
|
}
|
|
type stackt struct {
|
|
kind byte
|
|
key bool
|
|
}
|
|
var dst []byte
|
|
var stack []stackt
|
|
for i := 0; i < len(src); i++ {
|
|
if src[i] == '"' {
|
|
key := len(stack) > 0 && stack[len(stack)-1].key
|
|
if key {
|
|
dst = append(dst, style.Key[0]...)
|
|
} else {
|
|
dst = append(dst, style.String[0]...)
|
|
}
|
|
dst = apnd(dst, '"')
|
|
for i = i + 1; i < len(src); i++ {
|
|
dst = apnd(dst, src[i])
|
|
if src[i] == '"' {
|
|
j := i - 1
|
|
for ; ; j-- {
|
|
if src[j] != '\\' {
|
|
break
|
|
}
|
|
}
|
|
if (j-i)%2 != 0 {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if key {
|
|
dst = append(dst, style.Key[1]...)
|
|
} else {
|
|
dst = append(dst, style.String[1]...)
|
|
}
|
|
} else if src[i] == '{' || src[i] == '[' {
|
|
stack = append(stack, stackt{src[i], src[i] == '{'})
|
|
dst = apnd(dst, src[i])
|
|
} else if (src[i] == '}' || src[i] == ']') && len(stack) > 0 {
|
|
stack = stack[:len(stack)-1]
|
|
dst = apnd(dst, src[i])
|
|
} 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 = apnd(dst, src[i])
|
|
} else {
|
|
var kind byte
|
|
if (src[i] >= '0' && src[i] <= '9') || src[i] == '-' {
|
|
kind = '0'
|
|
dst = append(dst, style.Number[0]...)
|
|
} else if src[i] == 't' {
|
|
kind = 't'
|
|
dst = append(dst, style.True[0]...)
|
|
} else if src[i] == 'f' {
|
|
kind = 'f'
|
|
dst = append(dst, style.False[0]...)
|
|
} else if src[i] == 'n' {
|
|
kind = 'n'
|
|
dst = append(dst, style.Null[0]...)
|
|
} else {
|
|
dst = apnd(dst, src[i])
|
|
}
|
|
if kind != 0 {
|
|
for ; i < len(src); i++ {
|
|
if src[i] <= ' ' || src[i] == ',' || src[i] == ':' || src[i] == ']' || src[i] == '}' {
|
|
i--
|
|
break
|
|
}
|
|
dst = apnd(dst, src[i])
|
|
}
|
|
if kind == '0' {
|
|
dst = append(dst, style.Number[1]...)
|
|
} else if kind == 't' {
|
|
dst = append(dst, style.True[1]...)
|
|
} else if kind == 'f' {
|
|
dst = append(dst, style.False[1]...)
|
|
} else if kind == 'n' {
|
|
dst = append(dst, style.Null[1]...)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return dst
|
|
}
|