github.com/songzhibin97/gkit@v1.2.13/tools/pretty/pretty.go (about)

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