pretty.go raw

   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  func (arr *byKeyVal) Less(i, j int) bool {
 144  	if arr.isLess(i, j, byKey) {
 145  		return true
 146  	}
 147  	if arr.isLess(j, i, byKey) {
 148  		return false
 149  	}
 150  	return arr.isLess(i, j, byVal)
 151  }
 152  func (arr *byKeyVal) Swap(i, j int) {
 153  	arr.pairs[i], arr.pairs[j] = arr.pairs[j], arr.pairs[i]
 154  	arr.sorted = true
 155  }
 156  
 157  type byKind int
 158  
 159  const (
 160  	byKey byKind = 0
 161  	byVal byKind = 1
 162  )
 163  
 164  type jtype int
 165  
 166  const (
 167  	jnull jtype = iota
 168  	jfalse
 169  	jnumber
 170  	jstring
 171  	jtrue
 172  	jjson
 173  )
 174  
 175  func getjtype(v []byte) jtype {
 176  	if len(v) == 0 {
 177  		return jnull
 178  	}
 179  	switch v[0] {
 180  	case '"':
 181  		return jstring
 182  	case 'f':
 183  		return jfalse
 184  	case 't':
 185  		return jtrue
 186  	case 'n':
 187  		return jnull
 188  	case '[', '{':
 189  		return jjson
 190  	default:
 191  		return jnumber
 192  	}
 193  }
 194  
 195  func (arr *byKeyVal) isLess(i, j int, kind byKind) bool {
 196  	k1 := arr.json[arr.pairs[i].kstart:arr.pairs[i].kend]
 197  	k2 := arr.json[arr.pairs[j].kstart:arr.pairs[j].kend]
 198  	var v1, v2 []byte
 199  	if kind == byKey {
 200  		v1 = k1
 201  		v2 = k2
 202  	} else {
 203  		v1 = bytes.TrimSpace(arr.buf[arr.pairs[i].vstart:arr.pairs[i].vend])
 204  		v2 = bytes.TrimSpace(arr.buf[arr.pairs[j].vstart:arr.pairs[j].vend])
 205  		if len(v1) >= len(k1)+1 {
 206  			v1 = bytes.TrimSpace(v1[len(k1)+1:])
 207  		}
 208  		if len(v2) >= len(k2)+1 {
 209  			v2 = bytes.TrimSpace(v2[len(k2)+1:])
 210  		}
 211  	}
 212  	t1 := getjtype(v1)
 213  	t2 := getjtype(v2)
 214  	if t1 < t2 {
 215  		return true
 216  	}
 217  	if t1 > t2 {
 218  		return false
 219  	}
 220  	if t1 == jstring {
 221  		s1 := parsestr(v1)
 222  		s2 := parsestr(v2)
 223  		return string(s1) < string(s2)
 224  	}
 225  	if t1 == jnumber {
 226  		n1, _ := strconv.ParseFloat(string(v1), 64)
 227  		n2, _ := strconv.ParseFloat(string(v2), 64)
 228  		return n1 < n2
 229  	}
 230  	return string(v1) < string(v2)
 231  
 232  }
 233  
 234  func parsestr(s []byte) []byte {
 235  	for i := 1; i < len(s); i++ {
 236  		if s[i] == '\\' {
 237  			var str string
 238  			json.Unmarshal(s, &str)
 239  			return []byte(str)
 240  		}
 241  		if s[i] == '"' {
 242  			return s[1:i]
 243  		}
 244  	}
 245  	return nil
 246  }
 247  
 248  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) {
 249  	var ok bool
 250  	if width > 0 {
 251  		if pretty && open == '[' && max == -1 {
 252  			// here we try to create a single line array
 253  			max := width - (len(buf) - nl)
 254  			if max > 3 {
 255  				s1, s2 := len(buf), i
 256  				buf, i, _, ok = appendPrettyObject(buf, json, i, '[', ']', false, width, prefix, "", sortkeys, 0, 0, max)
 257  				if ok && len(buf)-s1 <= max {
 258  					return buf, i, nl, true
 259  				}
 260  				buf = buf[:s1]
 261  				i = s2
 262  			}
 263  		} else if max != -1 && open == '{' {
 264  			return buf, i, nl, false
 265  		}
 266  	}
 267  	buf = append(buf, open)
 268  	i++
 269  	var pairs []pair
 270  	if open == '{' && sortkeys {
 271  		pairs = make([]pair, 0, 8)
 272  	}
 273  	var n int
 274  	for ; i < len(json); i++ {
 275  		if json[i] <= ' ' {
 276  			continue
 277  		}
 278  		if json[i] == close {
 279  			if pretty {
 280  				if open == '{' && sortkeys {
 281  					buf = sortPairs(json, buf, pairs)
 282  				}
 283  				if n > 0 {
 284  					nl = len(buf)
 285  					if buf[nl-1] == ' ' {
 286  						buf[nl-1] = '\n'
 287  					} else {
 288  						buf = append(buf, '\n')
 289  					}
 290  				}
 291  				if buf[len(buf)-1] != open {
 292  					buf = appendTabs(buf, prefix, indent, tabs)
 293  				}
 294  			}
 295  			buf = append(buf, close)
 296  			return buf, i + 1, nl, open != '{'
 297  		}
 298  		if open == '[' || json[i] == '"' {
 299  			if n > 0 {
 300  				buf = append(buf, ',')
 301  				if width != -1 && open == '[' {
 302  					buf = append(buf, ' ')
 303  				}
 304  			}
 305  			var p pair
 306  			if pretty {
 307  				nl = len(buf)
 308  				if buf[nl-1] == ' ' {
 309  					buf[nl-1] = '\n'
 310  				} else {
 311  					buf = append(buf, '\n')
 312  				}
 313  				if open == '{' && sortkeys {
 314  					p.kstart = i
 315  					p.vstart = len(buf)
 316  				}
 317  				buf = appendTabs(buf, prefix, indent, tabs+1)
 318  			}
 319  			if open == '{' {
 320  				buf, i, nl, _ = appendPrettyString(buf, json, i, nl)
 321  				if sortkeys {
 322  					p.kend = i
 323  				}
 324  				buf = append(buf, ':')
 325  				if pretty {
 326  					buf = append(buf, ' ')
 327  				}
 328  			}
 329  			buf, i, nl, ok = appendPrettyAny(buf, json, i, pretty, width, prefix, indent, sortkeys, tabs+1, nl, max)
 330  			if max != -1 && !ok {
 331  				return buf, i, nl, false
 332  			}
 333  			if pretty && open == '{' && sortkeys {
 334  				p.vend = len(buf)
 335  				if p.kstart > p.kend || p.vstart > p.vend {
 336  					// bad data. disable sorting
 337  					sortkeys = false
 338  				} else {
 339  					pairs = append(pairs, p)
 340  				}
 341  			}
 342  			i--
 343  			n++
 344  		}
 345  	}
 346  	return buf, i, nl, open != '{'
 347  }
 348  func sortPairs(json, buf []byte, pairs []pair) []byte {
 349  	if len(pairs) == 0 {
 350  		return buf
 351  	}
 352  	vstart := pairs[0].vstart
 353  	vend := pairs[len(pairs)-1].vend
 354  	arr := byKeyVal{false, json, buf, pairs}
 355  	sort.Stable(&arr)
 356  	if !arr.sorted {
 357  		return buf
 358  	}
 359  	nbuf := make([]byte, 0, vend-vstart)
 360  	for i, p := range pairs {
 361  		nbuf = append(nbuf, buf[p.vstart:p.vend]...)
 362  		if i < len(pairs)-1 {
 363  			nbuf = append(nbuf, ',')
 364  			nbuf = append(nbuf, '\n')
 365  		}
 366  	}
 367  	return append(buf[:vstart], nbuf...)
 368  }
 369  
 370  func appendPrettyString(buf, json []byte, i, nl int) ([]byte, int, int, bool) {
 371  	s := i
 372  	i++
 373  	for ; i < len(json); i++ {
 374  		if json[i] == '"' {
 375  			var sc int
 376  			for j := i - 1; j > s; j-- {
 377  				if json[j] == '\\' {
 378  					sc++
 379  				} else {
 380  					break
 381  				}
 382  			}
 383  			if sc%2 == 1 {
 384  				continue
 385  			}
 386  			i++
 387  			break
 388  		}
 389  	}
 390  	return append(buf, json[s:i]...), i, nl, true
 391  }
 392  
 393  func appendPrettyNumber(buf, json []byte, i, nl int) ([]byte, int, int, bool) {
 394  	s := i
 395  	i++
 396  	for ; i < len(json); i++ {
 397  		if json[i] <= ' ' || json[i] == ',' || json[i] == ':' || json[i] == ']' || json[i] == '}' {
 398  			break
 399  		}
 400  	}
 401  	return append(buf, json[s:i]...), i, nl, true
 402  }
 403  
 404  func appendTabs(buf []byte, prefix, indent string, tabs int) []byte {
 405  	if len(prefix) != 0 {
 406  		buf = append(buf, prefix...)
 407  	}
 408  	if len(indent) == 2 && indent[0] == ' ' && indent[1] == ' ' {
 409  		for i := 0; i < tabs; i++ {
 410  			buf = append(buf, ' ', ' ')
 411  		}
 412  	} else {
 413  		for i := 0; i < tabs; i++ {
 414  			buf = append(buf, indent...)
 415  		}
 416  	}
 417  	return buf
 418  }
 419  
 420  // Style is the color style
 421  type Style struct {
 422  	Key, String, Number [2]string
 423  	True, False, Null   [2]string
 424  	Escape              [2]string
 425  	Brackets            [2]string
 426  	Append              func(dst []byte, c byte) []byte
 427  }
 428  
 429  func hexp(p byte) byte {
 430  	switch {
 431  	case p < 10:
 432  		return p + '0'
 433  	default:
 434  		return (p - 10) + 'a'
 435  	}
 436  }
 437  
 438  // TerminalStyle is for terminals
 439  var TerminalStyle *Style
 440  
 441  func init() {
 442  	TerminalStyle = &Style{
 443  		Key:      [2]string{"\x1B[1m\x1B[94m", "\x1B[0m"},
 444  		String:   [2]string{"\x1B[32m", "\x1B[0m"},
 445  		Number:   [2]string{"\x1B[33m", "\x1B[0m"},
 446  		True:     [2]string{"\x1B[36m", "\x1B[0m"},
 447  		False:    [2]string{"\x1B[36m", "\x1B[0m"},
 448  		Null:     [2]string{"\x1B[2m", "\x1B[0m"},
 449  		Escape:   [2]string{"\x1B[35m", "\x1B[0m"},
 450  		Brackets: [2]string{"\x1B[1m", "\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 = append(dst, style.Brackets[0]...)
 545  			dst = apnd(dst, src[i])
 546  			dst = append(dst, style.Brackets[1]...)
 547  		} else if (src[i] == '}' || src[i] == ']') && len(stack) > 0 {
 548  			stack = stack[:len(stack)-1]
 549  			dst = append(dst, style.Brackets[0]...)
 550  			dst = apnd(dst, src[i])
 551  			dst = append(dst, style.Brackets[1]...)
 552  		} else if (src[i] == ':' || src[i] == ',') && len(stack) > 0 && stack[len(stack)-1].kind == '{' {
 553  			stack[len(stack)-1].key = !stack[len(stack)-1].key
 554  			dst = append(dst, style.Brackets[0]...)
 555  			dst = apnd(dst, src[i])
 556  			dst = append(dst, style.Brackets[1]...)
 557  		} else {
 558  			var kind byte
 559  			if (src[i] >= '0' && src[i] <= '9') || src[i] == '-' || isNaNOrInf(src[i:]) {
 560  				kind = '0'
 561  				dst = append(dst, style.Number[0]...)
 562  			} else if src[i] == 't' {
 563  				kind = 't'
 564  				dst = append(dst, style.True[0]...)
 565  			} else if src[i] == 'f' {
 566  				kind = 'f'
 567  				dst = append(dst, style.False[0]...)
 568  			} else if src[i] == 'n' {
 569  				kind = 'n'
 570  				dst = append(dst, style.Null[0]...)
 571  			} else {
 572  				dst = apnd(dst, src[i])
 573  			}
 574  			if kind != 0 {
 575  				for ; i < len(src); i++ {
 576  					if src[i] <= ' ' || src[i] == ',' || src[i] == ':' || src[i] == ']' || src[i] == '}' {
 577  						i--
 578  						break
 579  					}
 580  					dst = apnd(dst, src[i])
 581  				}
 582  				if kind == '0' {
 583  					dst = append(dst, style.Number[1]...)
 584  				} else if kind == 't' {
 585  					dst = append(dst, style.True[1]...)
 586  				} else if kind == 'f' {
 587  					dst = append(dst, style.False[1]...)
 588  				} else if kind == 'n' {
 589  					dst = append(dst, style.Null[1]...)
 590  				}
 591  			}
 592  		}
 593  	}
 594  	return dst
 595  }
 596  
 597  // Spec strips out comments and trailing commas and convert the input to a
 598  // valid JSON per the official spec: https://tools.ietf.org/html/rfc8259
 599  //
 600  // The resulting JSON will always be the same length as the input and it will
 601  // include all of the same line breaks at matching offsets. This is to ensure
 602  // the result can be later processed by a external parser and that that
 603  // parser will report messages or errors with the correct offsets.
 604  func Spec(src []byte) []byte {
 605  	return spec(src, nil)
 606  }
 607  
 608  // SpecInPlace is the same as Spec, but this method reuses the input json
 609  // buffer to avoid allocations. Do not use the original bytes slice upon return.
 610  func SpecInPlace(src []byte) []byte {
 611  	return spec(src, src)
 612  }
 613  
 614  func spec(src, dst []byte) []byte {
 615  	dst = dst[:0]
 616  	for i := 0; i < len(src); i++ {
 617  		if src[i] == '/' {
 618  			if i < len(src)-1 {
 619  				if src[i+1] == '/' {
 620  					dst = append(dst, ' ', ' ')
 621  					i += 2
 622  					for ; i < len(src); i++ {
 623  						if src[i] == '\n' {
 624  							dst = append(dst, '\n')
 625  							break
 626  						} else if src[i] == '\t' || src[i] == '\r' {
 627  							dst = append(dst, src[i])
 628  						} else {
 629  							dst = append(dst, ' ')
 630  						}
 631  					}
 632  					continue
 633  				}
 634  				if src[i+1] == '*' {
 635  					dst = append(dst, ' ', ' ')
 636  					i += 2
 637  					for ; i < len(src)-1; i++ {
 638  						if src[i] == '*' && src[i+1] == '/' {
 639  							dst = append(dst, ' ', ' ')
 640  							i++
 641  							break
 642  						} else if src[i] == '\n' || src[i] == '\t' ||
 643  							src[i] == '\r' {
 644  							dst = append(dst, src[i])
 645  						} else {
 646  							dst = append(dst, ' ')
 647  						}
 648  					}
 649  					continue
 650  				}
 651  			}
 652  		}
 653  		dst = append(dst, src[i])
 654  		if src[i] == '"' {
 655  			for i = i + 1; i < len(src); i++ {
 656  				dst = append(dst, src[i])
 657  				if src[i] == '"' {
 658  					j := i - 1
 659  					for ; ; j-- {
 660  						if src[j] != '\\' {
 661  							break
 662  						}
 663  					}
 664  					if (j-i)%2 != 0 {
 665  						break
 666  					}
 667  				}
 668  			}
 669  		} else if src[i] == '}' || src[i] == ']' {
 670  			for j := len(dst) - 2; j >= 0; j-- {
 671  				if dst[j] <= ' ' {
 672  					continue
 673  				}
 674  				if dst[j] == ',' {
 675  					dst[j] = ' '
 676  				}
 677  				break
 678  			}
 679  		}
 680  	}
 681  	return dst
 682  }
 683