github.com/fufuok/utils@v1.0.10/xjson/pretty/pretty.go (about)

     1  package pretty
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"sort"
     7  	"strconv"
     8  )
     9  
    10  // Options is Pretty options
    11  type Options struct {
    12  	// Width is an max column width for single line arrays
    13  	// Default is 80
    14  	Width int
    15  	// Prefix is a prefix for all lines
    16  	// Default is an empty string
    17  	Prefix string
    18  	// Indent is the nested indentation
    19  	// Default is two spaces
    20  	Indent string
    21  	// SortKeys will sort the keys alphabetically
    22  	// Default is false
    23  	SortKeys bool
    24  }
    25  
    26  // DefaultOptions is the default options for pretty formats.
    27  var DefaultOptions = &Options{Width: 80, Prefix: "", Indent: "  ", SortKeys: false}
    28  
    29  // Pretty converts the input json into a more human readable format where each
    30  // element is on it's own line with clear indentation.
    31  func Pretty(json []byte) []byte { return PrettyOptions(json, nil) }
    32  
    33  // PrettyOptions is like Pretty but with customized options.
    34  func PrettyOptions(json []byte, opts *Options) []byte {
    35  	if opts == nil {
    36  		opts = DefaultOptions
    37  	}
    38  	buf := make([]byte, 0, len(json))
    39  	if len(opts.Prefix) != 0 {
    40  		buf = append(buf, opts.Prefix...)
    41  	}
    42  	buf, _, _, _ = appendPrettyAny(buf, json, 0, true,
    43  		opts.Width, opts.Prefix, opts.Indent, opts.SortKeys,
    44  		0, 0, -1)
    45  	if len(buf) > 0 {
    46  		buf = append(buf, '\n')
    47  	}
    48  	return buf
    49  }
    50  
    51  // Ugly removes insignificant space characters from the input json byte slice
    52  // and returns the compacted result.
    53  func Ugly(json []byte) []byte {
    54  	buf := make([]byte, 0, len(json))
    55  	return ugly(buf, json)
    56  }
    57  
    58  // UglyInPlace removes insignificant space characters from the input json
    59  // byte slice and returns the compacted result. This method reuses the
    60  // input json buffer to avoid allocations. Do not use the original bytes
    61  // slice upon return.
    62  func UglyInPlace(json []byte) []byte { return ugly(json, json) }
    63  
    64  func ugly(dst, src []byte) []byte {
    65  	dst = dst[:0]
    66  	for i := 0; i < len(src); i++ {
    67  		if src[i] > ' ' {
    68  			dst = append(dst, src[i])
    69  			if src[i] == '"' {
    70  				for i = i + 1; i < len(src); i++ {
    71  					dst = append(dst, src[i])
    72  					if src[i] == '"' {
    73  						j := i - 1
    74  						for ; ; j-- {
    75  							if src[j] != '\\' {
    76  								break
    77  							}
    78  						}
    79  						if (j-i)%2 != 0 {
    80  							break
    81  						}
    82  					}
    83  				}
    84  			}
    85  		}
    86  	}
    87  	return dst
    88  }
    89  
    90  func isNaNOrInf(src []byte) bool {
    91  	return src[0] == 'i' || // Inf
    92  		src[0] == 'I' || // inf
    93  		src[0] == '+' || // +Inf
    94  		src[0] == 'N' || // Nan
    95  		(src[0] == 'n' && len(src) > 1 && src[1] != 'u') // nan
    96  }
    97  
    98  func appendPrettyAny(buf, json []byte, i int, pretty bool, width int, prefix, indent string, sortkeys bool, tabs, nl, max int) ([]byte, int, int, bool) {
    99  	for ; i < len(json); i++ {
   100  		if json[i] <= ' ' {
   101  			continue
   102  		}
   103  		if json[i] == '"' {
   104  			return appendPrettyString(buf, json, i, nl)
   105  		}
   106  
   107  		if (json[i] >= '0' && json[i] <= '9') || json[i] == '-' || isNaNOrInf(json[i:]) {
   108  			return appendPrettyNumber(buf, json, i, nl)
   109  		}
   110  		if json[i] == '{' {
   111  			return appendPrettyObject(buf, json, i, '{', '}', pretty, width, prefix, indent, sortkeys, tabs, nl, max)
   112  		}
   113  		if json[i] == '[' {
   114  			return appendPrettyObject(buf, json, i, '[', ']', pretty, width, prefix, indent, sortkeys, tabs, nl, max)
   115  		}
   116  		switch json[i] {
   117  		case 't':
   118  			return append(buf, 't', 'r', 'u', 'e'), i + 4, nl, true
   119  		case 'f':
   120  			return append(buf, 'f', 'a', 'l', 's', 'e'), i + 5, nl, true
   121  		case 'n':
   122  			return append(buf, 'n', 'u', 'l', 'l'), i + 4, nl, true
   123  		}
   124  	}
   125  	return buf, i, nl, true
   126  }
   127  
   128  type pair struct {
   129  	kstart, kend int
   130  	vstart, vend int
   131  }
   132  
   133  type byKeyVal struct {
   134  	sorted bool
   135  	json   []byte
   136  	buf    []byte
   137  	pairs  []pair
   138  }
   139  
   140  func (arr *byKeyVal) Len() int {
   141  	return len(arr.pairs)
   142  }
   143  
   144  func (arr *byKeyVal) Less(i, j int) bool {
   145  	if arr.isLess(i, j, byKey) {
   146  		return true
   147  	}
   148  	if arr.isLess(j, i, byKey) {
   149  		return false
   150  	}
   151  	return arr.isLess(i, j, byVal)
   152  }
   153  
   154  func (arr *byKeyVal) Swap(i, j int) {
   155  	arr.pairs[i], arr.pairs[j] = arr.pairs[j], arr.pairs[i]
   156  	arr.sorted = true
   157  }
   158  
   159  type byKind int
   160  
   161  const (
   162  	byKey byKind = 0
   163  	byVal byKind = 1
   164  )
   165  
   166  type jtype int
   167  
   168  const (
   169  	jnull jtype = iota
   170  	jfalse
   171  	jnumber
   172  	jstring
   173  	jtrue
   174  	jjson
   175  )
   176  
   177  func getjtype(v []byte) jtype {
   178  	if len(v) == 0 {
   179  		return jnull
   180  	}
   181  	switch v[0] {
   182  	case '"':
   183  		return jstring
   184  	case 'f':
   185  		return jfalse
   186  	case 't':
   187  		return jtrue
   188  	case 'n':
   189  		return jnull
   190  	case '[', '{':
   191  		return jjson
   192  	default:
   193  		return jnumber
   194  	}
   195  }
   196  
   197  func (arr *byKeyVal) isLess(i, j int, kind byKind) bool {
   198  	k1 := arr.json[arr.pairs[i].kstart:arr.pairs[i].kend]
   199  	k2 := arr.json[arr.pairs[j].kstart:arr.pairs[j].kend]
   200  	var v1, v2 []byte
   201  	if kind == byKey {
   202  		v1 = k1
   203  		v2 = k2
   204  	} else {
   205  		v1 = bytes.TrimSpace(arr.buf[arr.pairs[i].vstart:arr.pairs[i].vend])
   206  		v2 = bytes.TrimSpace(arr.buf[arr.pairs[j].vstart:arr.pairs[j].vend])
   207  		if len(v1) >= len(k1)+1 {
   208  			v1 = bytes.TrimSpace(v1[len(k1)+1:])
   209  		}
   210  		if len(v2) >= len(k2)+1 {
   211  			v2 = bytes.TrimSpace(v2[len(k2)+1:])
   212  		}
   213  	}
   214  	t1 := getjtype(v1)
   215  	t2 := getjtype(v2)
   216  	if t1 < t2 {
   217  		return true
   218  	}
   219  	if t1 > t2 {
   220  		return false
   221  	}
   222  	if t1 == jstring {
   223  		s1 := parsestr(v1)
   224  		s2 := parsestr(v2)
   225  		return string(s1) < string(s2)
   226  	}
   227  	if t1 == jnumber {
   228  		n1, _ := strconv.ParseFloat(string(v1), 64)
   229  		n2, _ := strconv.ParseFloat(string(v2), 64)
   230  		return n1 < n2
   231  	}
   232  	return string(v1) < string(v2)
   233  }
   234  
   235  func parsestr(s []byte) []byte {
   236  	for i := 1; i < len(s); i++ {
   237  		if s[i] == '\\' {
   238  			var str string
   239  			json.Unmarshal(s, &str)
   240  			return []byte(str)
   241  		}
   242  		if s[i] == '"' {
   243  			return s[1:i]
   244  		}
   245  	}
   246  	return nil
   247  }
   248  
   249  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) {
   250  	var ok bool
   251  	if width > 0 {
   252  		if pretty && open == '[' && max == -1 {
   253  			// here we try to create a single line array
   254  			max := width - (len(buf) - nl)
   255  			if max > 3 {
   256  				s1, s2 := len(buf), i
   257  				buf, i, _, ok = appendPrettyObject(buf, json, i, '[', ']', false, width, prefix, "", sortkeys, 0, 0, max)
   258  				if ok && len(buf)-s1 <= max {
   259  					return buf, i, nl, true
   260  				}
   261  				buf = buf[:s1]
   262  				i = s2
   263  			}
   264  		} else if max != -1 && open == '{' {
   265  			return buf, i, nl, false
   266  		}
   267  	}
   268  	buf = append(buf, open)
   269  	i++
   270  	var pairs []pair
   271  	if open == '{' && sortkeys {
   272  		pairs = make([]pair, 0, 8)
   273  	}
   274  	var n int
   275  	for ; i < len(json); i++ {
   276  		if json[i] <= ' ' {
   277  			continue
   278  		}
   279  		if json[i] == close {
   280  			if pretty {
   281  				if open == '{' && sortkeys {
   282  					buf = sortPairs(json, buf, pairs)
   283  				}
   284  				if n > 0 {
   285  					nl = len(buf)
   286  					if buf[nl-1] == ' ' {
   287  						buf[nl-1] = '\n'
   288  					} else {
   289  						buf = append(buf, '\n')
   290  					}
   291  				}
   292  				if buf[len(buf)-1] != open {
   293  					buf = appendTabs(buf, prefix, indent, tabs)
   294  				}
   295  			}
   296  			buf = append(buf, close)
   297  			return buf, i + 1, nl, open != '{'
   298  		}
   299  		if open == '[' || json[i] == '"' {
   300  			if n > 0 {
   301  				buf = append(buf, ',')
   302  				if width != -1 && open == '[' {
   303  					buf = append(buf, ' ')
   304  				}
   305  			}
   306  			var p pair
   307  			if pretty {
   308  				nl = len(buf)
   309  				if buf[nl-1] == ' ' {
   310  					buf[nl-1] = '\n'
   311  				} else {
   312  					buf = append(buf, '\n')
   313  				}
   314  				if open == '{' && sortkeys {
   315  					p.kstart = i
   316  					p.vstart = len(buf)
   317  				}
   318  				buf = appendTabs(buf, prefix, indent, tabs+1)
   319  			}
   320  			if open == '{' {
   321  				buf, i, nl, _ = appendPrettyString(buf, json, i, nl)
   322  				if sortkeys {
   323  					p.kend = i
   324  				}
   325  				buf = append(buf, ':')
   326  				if pretty {
   327  					buf = append(buf, ' ')
   328  				}
   329  			}
   330  			buf, i, nl, ok = appendPrettyAny(buf, json, i, pretty, width, prefix, indent, sortkeys, tabs+1, nl, max)
   331  			if max != -1 && !ok {
   332  				return buf, i, nl, false
   333  			}
   334  			if pretty && open == '{' && sortkeys {
   335  				p.vend = len(buf)
   336  				if p.kstart > p.kend || p.vstart > p.vend {
   337  					// bad data. disable sorting
   338  					sortkeys = false
   339  				} else {
   340  					pairs = append(pairs, p)
   341  				}
   342  			}
   343  			i--
   344  			n++
   345  		}
   346  	}
   347  	return buf, i, nl, open != '{'
   348  }
   349  
   350  func sortPairs(json, buf []byte, pairs []pair) []byte {
   351  	if len(pairs) == 0 {
   352  		return buf
   353  	}
   354  	vstart := pairs[0].vstart
   355  	vend := pairs[len(pairs)-1].vend
   356  	arr := byKeyVal{false, json, buf, pairs}
   357  	sort.Stable(&arr)
   358  	if !arr.sorted {
   359  		return buf
   360  	}
   361  	nbuf := make([]byte, 0, vend-vstart)
   362  	for i, p := range pairs {
   363  		nbuf = append(nbuf, buf[p.vstart:p.vend]...)
   364  		if i < len(pairs)-1 {
   365  			nbuf = append(nbuf, ',')
   366  			nbuf = append(nbuf, '\n')
   367  		}
   368  	}
   369  	return append(buf[:vstart], nbuf...)
   370  }
   371  
   372  func appendPrettyString(buf, json []byte, i, nl int) ([]byte, int, int, bool) {
   373  	s := i
   374  	i++
   375  	for ; i < len(json); i++ {
   376  		if json[i] == '"' {
   377  			var sc int
   378  			for j := i - 1; j > s; j-- {
   379  				if json[j] == '\\' {
   380  					sc++
   381  				} else {
   382  					break
   383  				}
   384  			}
   385  			if sc%2 == 1 {
   386  				continue
   387  			}
   388  			i++
   389  			break
   390  		}
   391  	}
   392  	return append(buf, json[s:i]...), i, nl, true
   393  }
   394  
   395  func appendPrettyNumber(buf, json []byte, i, nl int) ([]byte, int, int, bool) {
   396  	s := i
   397  	i++
   398  	for ; i < len(json); i++ {
   399  		if json[i] <= ' ' || json[i] == ',' || json[i] == ':' || json[i] == ']' || json[i] == '}' {
   400  			break
   401  		}
   402  	}
   403  	return append(buf, json[s:i]...), i, nl, true
   404  }
   405  
   406  func appendTabs(buf []byte, prefix, indent string, tabs int) []byte {
   407  	if len(prefix) != 0 {
   408  		buf = append(buf, prefix...)
   409  	}
   410  	if len(indent) == 2 && indent[0] == ' ' && indent[1] == ' ' {
   411  		for i := 0; i < tabs; i++ {
   412  			buf = append(buf, ' ', ' ')
   413  		}
   414  	} else {
   415  		for i := 0; i < tabs; i++ {
   416  			buf = append(buf, indent...)
   417  		}
   418  	}
   419  	return buf
   420  }
   421  
   422  // Style is the color style
   423  type Style struct {
   424  	Key, String, Number [2]string
   425  	True, False, Null   [2]string
   426  	Escape              [2]string
   427  	Append              func(dst []byte, c byte) []byte
   428  }
   429  
   430  func hexp(p byte) byte {
   431  	switch {
   432  	case p < 10:
   433  		return p + '0'
   434  	default:
   435  		return (p - 10) + 'a'
   436  	}
   437  }
   438  
   439  // TerminalStyle is for terminals
   440  var TerminalStyle *Style
   441  
   442  func init() {
   443  	TerminalStyle = &Style{
   444  		Key:    [2]string{"\x1B[94m", "\x1B[0m"},
   445  		String: [2]string{"\x1B[92m", "\x1B[0m"},
   446  		Number: [2]string{"\x1B[93m", "\x1B[0m"},
   447  		True:   [2]string{"\x1B[96m", "\x1B[0m"},
   448  		False:  [2]string{"\x1B[96m", "\x1B[0m"},
   449  		Null:   [2]string{"\x1B[91m", "\x1B[0m"},
   450  		Escape: [2]string{"\x1B[35m", "\x1B[0m"},
   451  		Append: func(dst []byte, c byte) []byte {
   452  			if c < ' ' && (c != '\r' && c != '\n' && c != '\t' && c != '\v') {
   453  				dst = append(dst, "\\u00"...)
   454  				dst = append(dst, hexp((c>>4)&0xF))
   455  				return append(dst, hexp((c)&0xF))
   456  			}
   457  			return append(dst, c)
   458  		},
   459  	}
   460  }
   461  
   462  // Color will colorize the json. The style parma is used for customizing
   463  // the colors. Passing nil to the style param will use the default
   464  // TerminalStyle.
   465  func Color(src []byte, style *Style) []byte {
   466  	if style == nil {
   467  		style = TerminalStyle
   468  	}
   469  	apnd := style.Append
   470  	if apnd == nil {
   471  		apnd = func(dst []byte, c byte) []byte {
   472  			return append(dst, c)
   473  		}
   474  	}
   475  	type stackt struct {
   476  		kind byte
   477  		key  bool
   478  	}
   479  	var dst []byte
   480  	var stack []stackt
   481  	for i := 0; i < len(src); i++ {
   482  		if src[i] == '"' {
   483  			key := len(stack) > 0 && stack[len(stack)-1].key
   484  			if key {
   485  				dst = append(dst, style.Key[0]...)
   486  			} else {
   487  				dst = append(dst, style.String[0]...)
   488  			}
   489  			dst = apnd(dst, '"')
   490  			esc := false
   491  			uesc := 0
   492  			for i = i + 1; i < len(src); i++ {
   493  				if src[i] == '\\' {
   494  					if key {
   495  						dst = append(dst, style.Key[1]...)
   496  					} else {
   497  						dst = append(dst, style.String[1]...)
   498  					}
   499  					dst = append(dst, style.Escape[0]...)
   500  					dst = apnd(dst, src[i])
   501  					esc = true
   502  					if i+1 < len(src) && src[i+1] == 'u' {
   503  						uesc = 5
   504  					} else {
   505  						uesc = 1
   506  					}
   507  				} else if esc {
   508  					dst = apnd(dst, src[i])
   509  					if uesc == 1 {
   510  						esc = false
   511  						dst = append(dst, style.Escape[1]...)
   512  						if key {
   513  							dst = append(dst, style.Key[0]...)
   514  						} else {
   515  							dst = append(dst, style.String[0]...)
   516  						}
   517  					} else {
   518  						uesc--
   519  					}
   520  				} else {
   521  					dst = apnd(dst, src[i])
   522  				}
   523  				if src[i] == '"' {
   524  					j := i - 1
   525  					for ; ; j-- {
   526  						if src[j] != '\\' {
   527  							break
   528  						}
   529  					}
   530  					if (j-i)%2 != 0 {
   531  						break
   532  					}
   533  				}
   534  			}
   535  			if esc {
   536  				dst = append(dst, style.Escape[1]...)
   537  			} else if key {
   538  				dst = append(dst, style.Key[1]...)
   539  			} else {
   540  				dst = append(dst, style.String[1]...)
   541  			}
   542  		} else if src[i] == '{' || src[i] == '[' {
   543  			stack = append(stack, stackt{src[i], src[i] == '{'})
   544  			dst = apnd(dst, src[i])
   545  		} else if (src[i] == '}' || src[i] == ']') && len(stack) > 0 {
   546  			stack = stack[:len(stack)-1]
   547  			dst = apnd(dst, src[i])
   548  		} else if (src[i] == ':' || src[i] == ',') && len(stack) > 0 && stack[len(stack)-1].kind == '{' {
   549  			stack[len(stack)-1].key = !stack[len(stack)-1].key
   550  			dst = apnd(dst, src[i])
   551  		} else {
   552  			var kind byte
   553  			if (src[i] >= '0' && src[i] <= '9') || src[i] == '-' || isNaNOrInf(src[i:]) {
   554  				kind = '0'
   555  				dst = append(dst, style.Number[0]...)
   556  			} else if src[i] == 't' {
   557  				kind = 't'
   558  				dst = append(dst, style.True[0]...)
   559  			} else if src[i] == 'f' {
   560  				kind = 'f'
   561  				dst = append(dst, style.False[0]...)
   562  			} else if src[i] == 'n' {
   563  				kind = 'n'
   564  				dst = append(dst, style.Null[0]...)
   565  			} else {
   566  				dst = apnd(dst, src[i])
   567  			}
   568  			if kind != 0 {
   569  				for ; i < len(src); i++ {
   570  					if src[i] <= ' ' || src[i] == ',' || src[i] == ':' || src[i] == ']' || src[i] == '}' {
   571  						i--
   572  						break
   573  					}
   574  					dst = apnd(dst, src[i])
   575  				}
   576  				if kind == '0' {
   577  					dst = append(dst, style.Number[1]...)
   578  				} else if kind == 't' {
   579  					dst = append(dst, style.True[1]...)
   580  				} else if kind == 'f' {
   581  					dst = append(dst, style.False[1]...)
   582  				} else if kind == 'n' {
   583  					dst = append(dst, style.Null[1]...)
   584  				}
   585  			}
   586  		}
   587  	}
   588  	return dst
   589  }
   590  
   591  // Spec strips out comments and trailing commas and convert the input to a
   592  // valid JSON per the official spec: https://tools.ietf.org/html/rfc8259
   593  //
   594  // The resulting JSON will always be the same length as the input and it will
   595  // include all of the same line breaks at matching offsets. This is to ensure
   596  // the result can be later processed by a external parser and that that
   597  // parser will report messages or errors with the correct offsets.
   598  func Spec(src []byte) []byte {
   599  	return spec(src, nil)
   600  }
   601  
   602  // SpecInPlace is the same as Spec, but this method reuses the input json
   603  // buffer to avoid allocations. Do not use the original bytes slice upon return.
   604  func SpecInPlace(src []byte) []byte {
   605  	return spec(src, src)
   606  }
   607  
   608  func spec(src, dst []byte) []byte {
   609  	dst = dst[:0]
   610  	for i := 0; i < len(src); i++ {
   611  		if src[i] == '/' {
   612  			if i < len(src)-1 {
   613  				if src[i+1] == '/' {
   614  					dst = append(dst, ' ', ' ')
   615  					i += 2
   616  					for ; i < len(src); i++ {
   617  						if src[i] == '\n' {
   618  							dst = append(dst, '\n')
   619  							break
   620  						} else if src[i] == '\t' || src[i] == '\r' {
   621  							dst = append(dst, src[i])
   622  						} else {
   623  							dst = append(dst, ' ')
   624  						}
   625  					}
   626  					continue
   627  				}
   628  				if src[i+1] == '*' {
   629  					dst = append(dst, ' ', ' ')
   630  					i += 2
   631  					for ; i < len(src)-1; i++ {
   632  						if src[i] == '*' && src[i+1] == '/' {
   633  							dst = append(dst, ' ', ' ')
   634  							i++
   635  							break
   636  						} else if src[i] == '\n' || src[i] == '\t' ||
   637  							src[i] == '\r' {
   638  							dst = append(dst, src[i])
   639  						} else {
   640  							dst = append(dst, ' ')
   641  						}
   642  					}
   643  					continue
   644  				}
   645  			}
   646  		}
   647  		dst = append(dst, src[i])
   648  		if src[i] == '"' {
   649  			for i = i + 1; i < len(src); i++ {
   650  				dst = append(dst, src[i])
   651  				if src[i] == '"' {
   652  					j := i - 1
   653  					for ; ; j-- {
   654  						if src[j] != '\\' {
   655  							break
   656  						}
   657  					}
   658  					if (j-i)%2 != 0 {
   659  						break
   660  					}
   661  				}
   662  			}
   663  		} else if src[i] == '}' || src[i] == ']' {
   664  			for j := len(dst) - 2; j >= 0; j-- {
   665  				if dst[j] <= ' ' {
   666  					continue
   667  				}
   668  				if dst[j] == ',' {
   669  					dst[j] = ' '
   670  				}
   671  				break
   672  			}
   673  		}
   674  	}
   675  	return dst
   676  }