123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674 |
- package pretty
- import (
- "bytes"
- "encoding/json"
- "sort"
- "strconv"
- )
- // 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 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] <= ' ' {
- continue
- }
- if json[i] == '"' {
- return appendPrettyString(buf, json, i, nl)
- }
- if (json[i] >= '0' && json[i] <= '9') || json[i] == '-' || isNaNOrInf(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 byKeyVal struct {
- sorted bool
- json []byte
- buf []byte
- pairs []pair
- }
- func (arr *byKeyVal) Len() int {
- return len(arr.pairs)
- }
- func (arr *byKeyVal) Less(i, j int) bool {
- if arr.isLess(i, j, byKey) {
- return true
- }
- if arr.isLess(j, i, byKey) {
- return false
- }
- 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 {
- 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)
- if buf[nl-1] == ' ' {
- buf[nl-1] = '\n'
- } else {
- 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 && open == '[' {
- buf = append(buf, ' ')
- }
- }
- var p pair
- if pretty {
- nl = len(buf)
- if buf[nl-1] == ' ' {
- buf[nl-1] = '\n'
- } else {
- 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 := byKeyVal{false, json, buf, pairs}
- sort.Stable(&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
- Escape [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
- 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"},
- 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, '"')
- esc := false
- uesc := 0
- for i = i + 1; i < len(src); i++ {
- if src[i] == '\\' {
- if key {
- dst = append(dst, style.Key[1]...)
- } else {
- dst = append(dst, style.String[1]...)
- }
- dst = append(dst, style.Escape[0]...)
- dst = apnd(dst, src[i])
- esc = true
- if i+1 < len(src) && src[i+1] == 'u' {
- uesc = 5
- } else {
- uesc = 1
- }
- } else if esc {
- dst = apnd(dst, src[i])
- if uesc == 1 {
- esc = false
- dst = append(dst, style.Escape[1]...)
- if key {
- dst = append(dst, style.Key[0]...)
- } else {
- dst = append(dst, style.String[0]...)
- }
- } else {
- uesc--
- }
- } else {
- dst = apnd(dst, src[i])
- }
- if src[i] == '"' {
- j := i - 1
- for ; ; j-- {
- if src[j] != '\\' {
- break
- }
- }
- if (j-i)%2 != 0 {
- break
- }
- }
- }
- if esc {
- dst = append(dst, style.Escape[1]...)
- } else 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] == '-' || isNaNOrInf(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
- }
- // 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
- }
|