github.com/nikandfor/tlog@v0.21.3/console.go (about) 1 package tlog 2 3 import ( 4 "bytes" 5 "encoding/hex" 6 "fmt" 7 "io" 8 "path/filepath" 9 "runtime/debug" 10 "strconv" 11 "strings" 12 "time" 13 "unsafe" 14 15 "github.com/nikandfor/errors" 16 "github.com/nikandfor/hacked/hfmt" 17 "github.com/nikandfor/hacked/htime" 18 "github.com/nikandfor/loc" 19 "golang.org/x/term" 20 21 "github.com/nikandfor/tlog/low" 22 "github.com/nikandfor/tlog/tlwire" 23 ) 24 25 type ( 26 ConsoleWriter struct { //nolint:maligned 27 io.Writer 28 Flags int 29 30 d tlwire.Decoder 31 32 addpad int // padding for the next pair 33 b, h low.Buf // buf, header 34 lasttime low.Buf 35 36 ls, lastls []byte 37 38 Colorize bool 39 PadEmptyMessage bool 40 AllLabels bool 41 AllCallers bool 42 43 LevelWidth int 44 MessageWidth int 45 IDWidth int 46 Shortfile int 47 Funcname int 48 MaxValPad int 49 50 TimeFormat string 51 TimeLocation *time.Location 52 DurationFormat string 53 DurationDiv time.Duration 54 FloatFormat string 55 FloatChar byte 56 FloatPrecision int 57 CallerFormat string 58 BytesFormat string 59 60 StringOnNewLineMinLen int 61 62 PairSeparator string 63 KVSeparator string 64 65 QuoteChars string 66 QuoteAnyValue bool 67 QuoteEmptyValue bool 68 69 ColorScheme 70 71 pad map[string]int 72 } 73 74 ColorScheme struct { 75 TimeColor []byte 76 TimeChangeColor []byte // if different from TimeColor 77 FileColor []byte 78 FuncColor []byte 79 MessageColor []byte 80 KeyColor []byte 81 ValColor []byte 82 LevelColor struct { 83 Info []byte 84 Warn []byte 85 Error []byte 86 Fatal []byte 87 Debug []byte 88 } 89 } 90 ) 91 92 const ( // console writer flags 93 Ldate = 1 << iota 94 Ltime 95 Lseconds 96 Lmilliseconds 97 Lmicroseconds 98 Lshortfile 99 Llongfile 100 Ltypefunc // pkg.(*Type).Func 101 Lfuncname // Func 102 LUTC 103 Lloglevel // log level 104 105 LstdFlags = Ldate | Ltime 106 LdetFlags = Ldate | Ltime | Lmicroseconds | Lshortfile | Lloglevel 107 108 Lnone = 0 109 ) 110 111 const ( 112 cfHex = 1 << iota 113 ) 114 115 var ( 116 ResetColor = Color(0) 117 118 DefaultColorScheme = ColorScheme{ 119 TimeColor: Color(90), 120 TimeChangeColor: Color(38, 5, 244, 1), 121 FileColor: Color(90), 122 FuncColor: Color(90), 123 KeyColor: Color(36), 124 LevelColor: struct { 125 Info []byte 126 Warn []byte 127 Error []byte 128 Fatal []byte 129 Debug []byte 130 }{ 131 Info: Color(90), 132 Warn: Color(31), 133 Error: Color(31, 1), 134 Fatal: Color(31, 1), 135 Debug: Color(90), 136 }, 137 } 138 ) 139 140 func NewConsoleWriter(w io.Writer, f int) *ConsoleWriter { 141 fd := -1 142 143 switch f := w.(type) { 144 case interface{ Fd() uintptr }: 145 fd = int(f.Fd()) 146 case interface{ Fd() int }: 147 fd = f.Fd() 148 } 149 150 colorize := term.IsTerminal(fd) 151 152 return &ConsoleWriter{ 153 Writer: w, 154 Flags: f, 155 156 Colorize: colorize, 157 PadEmptyMessage: true, 158 159 LevelWidth: 3, 160 Shortfile: 18, 161 Funcname: 16, 162 MessageWidth: 30, 163 IDWidth: 8, 164 MaxValPad: 24, 165 166 TimeFormat: "2006-01-02_15:04:05.000Z0700", 167 //DurationFormat: "%v", 168 FloatChar: 'f', 169 FloatPrecision: 5, 170 CallerFormat: "%v", 171 172 StringOnNewLineMinLen: 71, 173 174 PairSeparator: " ", 175 KVSeparator: "=", 176 177 QuoteChars: "`\"' ()[]{}*", 178 QuoteEmptyValue: true, 179 180 ColorScheme: DefaultColorScheme, 181 182 pad: make(map[string]int), 183 } 184 } 185 186 func (w *ConsoleWriter) Write(p []byte) (i int, err error) { 187 defer func() { 188 perr := recover() 189 190 if err == nil && perr == nil { 191 return 192 } 193 194 if perr != nil { 195 fmt.Fprintf(w.Writer, "panic: %v (pos %x)\n", perr, i) 196 } else { 197 fmt.Fprintf(w.Writer, "parse error: %+v (pos %x)\n", err, i) 198 } 199 fmt.Fprintf(w.Writer, "dump\n%v", tlwire.Dump(p)) 200 fmt.Fprintf(w.Writer, "hex dump\n%v", hex.Dump(p)) 201 202 s := debug.Stack() 203 fmt.Fprintf(w.Writer, "%s", s) 204 }() 205 206 if w.PairSeparator == "" { 207 w.PairSeparator = " " 208 } 209 210 if w.KVSeparator == "" { 211 w.KVSeparator = "=" 212 } 213 214 h := w.h 215 216 more: 217 w.addpad = 0 218 219 var t time.Time 220 var pc loc.PC 221 var lv LogLevel 222 var tp EventKind 223 var m []byte 224 w.ls = w.ls[:0] 225 b := w.b 226 227 tag, els, i := w.d.Tag(p, i) 228 if tag != tlwire.Map { 229 return 0, errors.New("expected map") 230 } 231 232 var k []byte 233 var sub int64 234 for el := 0; els == -1 || el < int(els); el++ { 235 if els == -1 && w.d.Break(p, &i) { 236 break 237 } 238 239 pairst := i 240 241 k, i = w.d.Bytes(p, i) 242 if len(k) == 0 { 243 return 0, errors.New("empty key") 244 } 245 246 st := i 247 248 tag, sub, i = w.d.Tag(p, i) 249 if tag != tlwire.Semantic { 250 b, i = w.appendPair(b, p, k, st) 251 continue 252 } 253 254 // println(fmt.Sprintf("key %s tag %x %x", k, tag, sub)) 255 256 switch { 257 case sub == tlwire.Time && string(k) == KeyTimestamp: 258 t, i = w.d.Time(p, st) 259 case sub == tlwire.Caller && string(k) == KeyCaller: 260 var pcs loc.PCs 261 262 pc, pcs, i = w.d.Callers(p, st) 263 264 if w.AllCallers && pcs != nil { 265 b, i = w.appendPair(b, p, k, st) 266 } 267 case sub == WireMessage && string(k) == KeyMessage: 268 m, i = w.d.Bytes(p, i) 269 case sub == WireLogLevel && string(k) == KeyLogLevel && w.Flags&Lloglevel != 0: 270 i = lv.TlogParse(p, st) 271 case sub == WireEventKind && string(k) == KeyEventKind: 272 _ = tp.TlogParse(p, st) 273 274 b, i = w.appendPair(b, p, k, st) 275 case sub == WireLabel: 276 i = w.d.Skip(p, st) 277 w.ls = append(w.ls, p[pairst:i]...) 278 default: 279 b, i = w.appendPair(b, p, k, st) 280 } 281 } 282 283 h = w.appendHeader(h, t, lv, pc, m, len(b)) 284 285 h = append(h, b...) 286 287 if w.AllLabels || !bytes.Equal(w.lastls, w.ls) { 288 h = w.convertLabels(h, w.ls) 289 w.lastls = append(w.lastls[:0], w.ls...) 290 } 291 292 h.NewLine() 293 294 if i < len(p) { 295 goto more 296 } 297 298 w.b = b[:0] 299 w.h = h[:0] 300 301 _, err = w.Writer.Write(h) 302 303 return len(p), err 304 } 305 306 func (w *ConsoleWriter) convertLabels(b, p []byte) []byte { 307 var k []byte 308 i := 0 309 310 for i != len(p) { 311 k, i = w.d.Bytes(p, i) 312 if len(k) == 0 { 313 panic("empty key") 314 } 315 316 b, i = w.appendPair(b, p, k, i) 317 } 318 319 return b 320 } 321 322 func (w *ConsoleWriter) appendHeader(b []byte, t time.Time, lv LogLevel, pc loc.PC, m []byte, blen int) []byte { 323 var fname, file string 324 line := -1 325 326 if w.Flags&(Ldate|Ltime|Lmilliseconds|Lmicroseconds) != 0 { 327 b = w.appendTime(b, t) 328 329 b = append(b, ' ', ' ') 330 } 331 332 if w.Flags&Lloglevel != 0 { 333 var col []byte 334 switch { 335 case !w.Colorize: 336 // break 337 case lv == Info: 338 col = w.LevelColor.Info 339 case lv == Warn: 340 col = w.LevelColor.Warn 341 case lv == Error: 342 col = w.LevelColor.Error 343 case lv >= Fatal: 344 col = w.LevelColor.Fatal 345 default: 346 col = w.LevelColor.Debug 347 } 348 349 if col != nil { 350 b = append(b, col...) 351 } 352 353 i := len(b) 354 b = append(b, low.Spaces[:w.LevelWidth]...) 355 356 switch lv { 357 case Info: 358 copy(b[i:], "INFO") 359 case Warn: 360 copy(b[i:], "WARN") 361 case Error: 362 copy(b[i:], "ERROR") 363 case Fatal: 364 copy(b[i:], "FATAL") 365 default: 366 b = hfmt.Appendf(b[:i], "%*x", w.LevelWidth, lv) 367 } 368 369 end := len(b) 370 371 if col != nil { 372 b = append(b, ResetColor...) 373 } 374 375 if pad := i + w.LevelWidth + 2 - end; pad > 0 { 376 b = append(b, low.Spaces[:pad]...) 377 } 378 } 379 380 if w.Flags&(Llongfile|Lshortfile) != 0 { 381 fname, file, line = pc.NameFileLine() 382 383 if w.Colorize && len(w.FileColor) != 0 { 384 b = append(b, w.FileColor...) 385 } 386 387 if w.Flags&Lshortfile != 0 { 388 file = filepath.Base(file) 389 390 n := 1 391 for q := line; q != 0; q /= 10 { 392 n++ 393 } 394 395 i := len(b) 396 397 b = append(b, low.Spaces[:w.Shortfile]...) 398 b = append(b[:i], file...) 399 400 e := len(b) 401 b = b[:i+w.Shortfile] 402 403 if len(file)+n > w.Shortfile { 404 i = i + w.Shortfile - n 405 } else { 406 i = e 407 } 408 409 b[i] = ':' 410 for q, j := line, n-1; j >= 1; j-- { 411 b[i+j] = byte(q%10) + '0' 412 q /= 10 413 } 414 } else { 415 b = append(b, file...) 416 417 n := 1 418 for q := line; q != 0; q /= 10 { 419 n++ 420 } 421 422 i := len(b) 423 b = append(b, ": "[:n]...) 424 425 for q, j := line, n-1; j >= 1; j-- { 426 b[i+j] = byte(q%10) + '0' 427 q /= 10 428 } 429 } 430 431 if w.Colorize && len(w.FileColor) != 0 { 432 b = append(b, ResetColor...) 433 } 434 435 b = append(b, ' ', ' ') 436 } 437 438 if w.Flags&(Ltypefunc|Lfuncname) != 0 { 439 if line == -1 { 440 fname, _, _ = pc.NameFileLine() 441 } 442 fname = filepath.Base(fname) 443 444 if w.Colorize && len(w.FuncColor) != 0 { 445 b = append(b, w.FuncColor...) 446 } 447 448 if w.Flags&Lfuncname != 0 { 449 p := strings.Index(fname, ").") 450 if p == -1 { 451 p = strings.IndexByte(fname, '.') 452 fname = fname[p+1:] 453 } else { 454 fname = fname[p+2:] 455 } 456 457 if l := len(fname); l <= w.Funcname { 458 i := len(b) 459 b = append(b, low.Spaces[:w.Funcname]...) 460 b = append(b[:i], fname...) 461 b = b[:i+w.Funcname] 462 } else { 463 b = append(b, fname[:w.Funcname]...) 464 j := 1 465 for { 466 q := fname[l-j] 467 if q < '0' || '9' < q { 468 break 469 } 470 b[len(b)-j] = q 471 j++ 472 } 473 } 474 } else { 475 b = append(b, fname...) 476 } 477 478 if w.Colorize && len(w.FuncColor) != 0 { 479 b = append(b, ResetColor...) 480 } 481 482 b = append(b, ' ', ' ') 483 } 484 485 if len(m) != 0 { 486 if w.Colorize && len(w.MessageColor) != 0 { 487 b = append(b, w.MessageColor...) 488 } 489 490 b = append(b, m...) 491 492 if w.Colorize && len(w.MessageColor) != 0 { 493 b = append(b, ResetColor...) 494 } 495 } 496 497 if len(m) >= w.MessageWidth && blen != 0 { 498 b = append(b, ' ', ' ') 499 } 500 501 if (w.PadEmptyMessage || len(m) != 0) && len(m) < w.MessageWidth && blen != 0 { 502 b = append(b, low.Spaces[:w.MessageWidth-len(m)]...) 503 } 504 505 return b 506 } 507 508 func (w *ConsoleWriter) appendTime(b []byte, t time.Time) []byte { 509 if w.Flags&LUTC != 0 { 510 t = t.UTC() 511 } 512 513 var Y, M, D, h, m, s int 514 if w.Flags&(Ldate|Ltime) != 0 { 515 Y, M, D, h, m, s = htime.DateClock(t) 516 } 517 518 if w.Colorize && len(w.TimeColor) != 0 { 519 b = append(b, w.TimeColor...) 520 } 521 522 ts := len(b) 523 524 if w.Flags&Ldate != 0 { 525 b = append(b, 526 byte(Y/1000)+'0', 527 byte(Y/100%10)+'0', 528 byte(Y/10%10)+'0', 529 byte(Y/1%10)+'0', 530 '-', 531 byte(M/10)+'0', 532 byte(M%10)+'0', 533 '-', 534 byte(D/10)+'0', 535 byte(D%10)+'0', 536 ) 537 } 538 if w.Flags&Ltime != 0 { 539 if w.Flags&Ldate != 0 { 540 b = append(b, '_') 541 } 542 543 b = append(b, 544 byte(h/10)+'0', 545 byte(h%10)+'0', 546 ':', 547 byte(m/10)+'0', 548 byte(m%10)+'0', 549 ':', 550 byte(s/10)+'0', 551 byte(s%10)+'0', 552 ) 553 } 554 if w.Flags&(Lmilliseconds|Lmicroseconds) != 0 { 555 if w.Flags&(Ldate|Ltime) != 0 { 556 b = append(b, '.') 557 } 558 559 ns := t.Nanosecond() / 1e3 560 if w.Flags&Lmilliseconds != 0 { 561 ns /= 1000 562 563 b = append(b, 564 byte(ns/100%10)+'0', 565 byte(ns/10%10)+'0', 566 byte(ns/1%10)+'0', 567 ) 568 } else { 569 b = append(b, 570 byte(ns/100000%10)+'0', 571 byte(ns/10000%10)+'0', 572 byte(ns/1000%10)+'0', 573 byte(ns/100%10)+'0', 574 byte(ns/10%10)+'0', 575 byte(ns/1%10)+'0', 576 ) 577 } 578 } 579 580 if w.Colorize && len(w.TimeChangeColor) != 0 { 581 c := common(b[ts:], w.lasttime) 582 ts += c 583 w.lasttime = append(w.lasttime[:c], b[ts:]...) 584 585 if c != 0 && ts != len(b) { 586 b = append(b, w.TimeChangeColor...) 587 588 copy(b[ts+len(w.TimeChangeColor):], b[ts:]) 589 copy(b[ts:], w.TimeChangeColor) 590 } 591 } 592 593 if w.Colorize && (len(w.TimeColor) != 0 || len(w.TimeChangeColor) != 0) { 594 b = append(b, ResetColor...) 595 } 596 597 return b 598 } 599 600 func (w *ConsoleWriter) appendPair(b, p, k []byte, st int) (_ []byte, i int) { 601 i = st 602 603 if w.addpad != 0 { 604 b = append(b, low.Spaces[:w.addpad]...) 605 w.addpad = 0 606 } 607 608 if len(b) != 0 { 609 b = append(b, w.PairSeparator...) 610 } 611 612 if w.Colorize && len(w.KeyColor) != 0 { 613 b = append(b, w.KeyColor...) 614 } 615 616 b = append(b, k...) 617 618 b = append(b, w.KVSeparator...) 619 620 if w.Colorize && len(w.ValColor) != 0 { 621 b = append(b, w.ValColor...) 622 } else if w.Colorize && len(w.KeyColor) != 0 { 623 b = append(b, ResetColor...) 624 } 625 626 vst := len(b) 627 628 b, i = w.ConvertValue(b, p, i, 0) 629 630 vw := len(b) - vst 631 632 // NOTE: Value width can be incorrect for non-ascii symbols. 633 // We can calc it by iterating utf8.DecodeRune() but should we? 634 635 if w.Colorize && len(w.ValColor) != 0 { 636 b = append(b, ResetColor...) 637 } 638 639 nw := w.pad[low.UnsafeBytesToString(k)] 640 641 if vw < nw { 642 w.addpad = nw - vw 643 } 644 645 if nw < vw && vw <= w.MaxValPad { 646 if vw > w.MaxValPad { 647 vw = w.MaxValPad 648 } 649 650 w.pad[string(k)] = vw 651 } 652 653 return b, i 654 } 655 656 func (w *ConsoleWriter) ConvertValue(b, p []byte, st, ff int) (_ []byte, i int) { 657 tag, sub, i := w.d.Tag(p, st) 658 659 switch tag { 660 case tlwire.Int, tlwire.Neg: 661 var v uint64 662 v, i = w.d.Unsigned(p, st) 663 664 base := 10 665 if tag == tlwire.Neg { 666 b = append(b, '-') 667 } 668 669 if ff&cfHex != 0 { 670 b = append(b, "0x"...) 671 base = 16 672 } 673 674 b = strconv.AppendUint(b, v, base) 675 case tlwire.Bytes, tlwire.String: 676 var s []byte 677 s, i = w.d.Bytes(p, st) 678 679 if w.StringOnNewLineMinLen != 0 && len(s) >= w.StringOnNewLineMinLen { 680 b = append(b, '\\', '\n') 681 682 if tag == tlwire.Bytes { 683 h := hex.Dumper(noescapeByteWriter(&b)) 684 685 _, _ = h.Write(s) 686 _ = h.Close() 687 } else { 688 b = append(b, s...) 689 690 if s[len(s)-1] != '\n' { 691 b = append(b, '\n') 692 } 693 } 694 695 break 696 } 697 698 if tag == tlwire.Bytes { 699 if w.BytesFormat != "" { 700 b = hfmt.Appendf(b, w.BytesFormat, s) 701 break 702 } 703 704 if ff&cfHex != 0 { 705 b = hfmt.Appendf(b, "%x", s) 706 break 707 } 708 } 709 710 quote := tag == tlwire.Bytes || w.QuoteAnyValue || len(s) == 0 && w.QuoteEmptyValue 711 if !quote { 712 for _, c := range s { 713 if c < 0x20 || c >= 0x80 { 714 quote = true 715 break 716 } 717 for _, q := range w.QuoteChars { 718 if byte(q) == c { 719 quote = true 720 break 721 } 722 } 723 } 724 } 725 726 if quote { 727 ss := low.UnsafeBytesToString(s) 728 b = strconv.AppendQuote(b, ss) 729 } else { 730 b = append(b, s...) 731 } 732 case tlwire.Array: 733 b = append(b, '[') 734 735 for el := 0; sub == -1 || el < int(sub); el++ { 736 if sub == -1 && w.d.Break(p, &i) { 737 break 738 } 739 740 if el != 0 { 741 b = append(b, ' ') 742 } 743 744 b, i = w.ConvertValue(b, p, i, ff) 745 } 746 747 b = append(b, ']') 748 case tlwire.Map: 749 b = append(b, '{') 750 751 for el := 0; sub == -1 || el < int(sub); el++ { 752 if sub == -1 && w.d.Break(p, &i) { 753 break 754 } 755 756 if el != 0 { 757 b = append(b, ' ') 758 } 759 760 b, i = w.ConvertValue(b, p, i, ff) 761 762 b = append(b, ':') 763 764 b, i = w.ConvertValue(b, p, i, ff) 765 } 766 767 b = append(b, '}') 768 case tlwire.Special: 769 switch sub { 770 case tlwire.None: 771 // none 772 case tlwire.False: 773 b = append(b, "false"...) 774 case tlwire.True: 775 b = append(b, "true"...) 776 case tlwire.Nil: 777 b = append(b, "<nil>"...) 778 case tlwire.Undefined: 779 b = append(b, "<undef>"...) 780 case tlwire.Float64, tlwire.Float32, tlwire.Float8: 781 var f float64 782 f, i = w.d.Float(p, st) 783 784 if w.FloatFormat != "" { 785 b = hfmt.Appendf(b, w.FloatFormat, f) 786 } else { 787 b = strconv.AppendFloat(b, f, w.FloatChar, w.FloatPrecision, 64) 788 } 789 default: 790 panic(sub) 791 } 792 case tlwire.Semantic: 793 switch sub { 794 case tlwire.Time: 795 var t time.Time 796 t, i = w.d.Time(p, st) 797 798 if w.TimeFormat == "" { 799 b = strconv.AppendInt(b, t.UnixNano(), 10) 800 break 801 } 802 803 if w.TimeLocation != nil { 804 t = t.In(w.TimeLocation) 805 } 806 807 b = t.AppendFormat(b, w.TimeFormat) 808 case tlwire.Duration: 809 var d time.Duration 810 d, i = w.d.Duration(p, st) 811 812 switch { 813 case w.DurationFormat != "" && w.DurationDiv != 0: 814 b = hfmt.Appendf(b, w.DurationFormat, float64(d/w.DurationDiv)) 815 case w.DurationFormat != "": 816 b = hfmt.Appendf(b, w.DurationFormat, d) 817 default: 818 b = w.AppendDuration(b, d) 819 } 820 case WireID: 821 var id ID 822 i = id.TlogParse(p, st) 823 824 st := len(b) 825 b = append(b, "123456789_123456789_123456789_12"[:w.IDWidth]...) 826 id.FormatTo(b[st:], 'v') 827 case tlwire.Hex: 828 b, i = w.ConvertValue(b, p, i, ff|cfHex) 829 case tlwire.Caller: 830 var pc loc.PC 831 var pcs loc.PCs 832 833 pc, pcs, i = w.d.Callers(p, st) 834 835 if pcs == nil { 836 b = hfmt.Appendf(b, w.CallerFormat, pc) 837 break 838 } 839 840 b = append(b, '[') 841 for i, pc := range pcs { 842 if i != 0 { 843 b = append(b, ' ') 844 } 845 846 b = hfmt.Appendf(b, w.CallerFormat, pc) 847 } 848 b = append(b, ']') 849 default: 850 b, i = w.ConvertValue(b, p, i, ff) 851 } 852 default: 853 panic(tag) 854 } 855 856 return b, i 857 } 858 859 func (w *ConsoleWriter) AppendDuration(b []byte, d time.Duration) []byte { 860 if d == 0 { 861 return append(b, ' ', ' ', '0', 's') 862 } 863 864 var buf [32]byte 865 866 if d >= 99*time.Second { 867 const MaxGroups = 2 868 group := 0 869 i := 0 870 871 if d < 0 { 872 d = -d 873 b = append(b, '-') 874 } 875 876 add := func(d, unit time.Duration, suff byte) time.Duration { 877 if group == 0 && d < unit && unit > time.Second || group >= MaxGroups { 878 return d 879 } 880 881 x := int(d / unit) 882 d = d % unit 883 group++ 884 885 if group == MaxGroups && d >= unit/2 { 886 x++ 887 } 888 889 w := width(x) 890 i += w 891 for j := 1; j <= w; j++ { 892 buf[i-j] = byte(x%10) + '0' 893 x /= 10 894 } 895 896 buf[i] = suff 897 i++ 898 899 return d 900 } 901 902 d = add(d, 24*time.Hour, 'd') 903 d = add(d, time.Hour, 'h') 904 d = add(d, time.Minute, 'm') 905 d = add(d, time.Second, 's') 906 907 return append(b, buf[:i]...) 908 } 909 910 neg := d < 0 911 if neg { 912 d = -d 913 } 914 915 end := len(buf) - 4 916 i := end 917 918 for d != 0 { 919 i-- 920 buf[i] = byte(d%10) + '0' 921 d /= 10 922 } 923 924 buf[i-1] = '0' // leading zero for possible carry 925 926 j := i + 3 927 buf[j] += 5 // round 928 929 for j >= i-1 && buf[j] > '9' { // move carry 930 buf[j] = '0' 931 j-- 932 buf[j]++ 933 } 934 935 j = end 936 u := -1 937 938 for buf[j-2] != 0 || buf[j-1] == '1' { // find suitable units 939 j -= 3 940 u++ 941 } 942 943 i = j // beginning 944 945 for buf[j] == '0' || buf[j] == 0 { // leading spaces 946 buf[j] = ' ' 947 j++ 948 } 949 950 digit := j 951 952 j += 3 953 954 // insert point 955 if j-1 > i+3 { 956 buf[j-1] = buf[j-2] 957 } 958 if j-2 > i+3 { 959 buf[j-2] = buf[j-3] 960 } 961 962 buf[i+3] = '.' 963 964 if j > end { 965 j = end 966 } 967 968 for j > i+3 && (buf[j-1] == '0' || buf[j-1] == '.') { // trailing zeros 969 j-- 970 } 971 972 suff := []string{"ns", "µs", "ms", "s", "m"} 973 j += copy(buf[j:], suff[u]) 974 975 if neg { 976 buf[digit-1] = '-' 977 978 if digit == i { 979 i-- 980 } 981 } 982 983 return append(b, buf[i:j]...) 984 } 985 986 func width(n int) (w int) { 987 q := 10 988 w = 1 989 990 for q <= n { 991 w++ 992 q *= 10 993 } 994 995 return w 996 } 997 998 func Color(c ...int) (r []byte) { 999 if len(c) == 0 { 1000 return nil 1001 } 1002 1003 r = append(r, '\x1b', '[') 1004 1005 for i, c := range c { 1006 if i != 0 { 1007 r = append(r, ';') 1008 } 1009 1010 switch { 1011 case c < 10: 1012 r = append(r, '0'+byte(c%10)) 1013 case c < 100: 1014 r = append(r, '0'+byte(c/10), '0'+byte(c%10)) 1015 default: 1016 r = append(r, '0'+byte(c/100), '0'+byte(c/10%10), '0'+byte(c%10)) 1017 } 1018 } 1019 1020 r = append(r, 'm') 1021 1022 return r 1023 } 1024 1025 func common(x, y []byte) (n int) { 1026 for n < len(y) && x[n] == y[n] { 1027 n++ 1028 } 1029 1030 return 1031 } 1032 1033 func noescapeByteWriter(b *[]byte) *low.Buf { 1034 // return (*low.Buf)(b) 1035 return (*low.Buf)(noescape(unsafe.Pointer(b))) 1036 }