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 }