github.com/nikandfor/tlog@v0.21.5-0.20231108111739-3ef89426a96d/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.False: 771 b = append(b, "false"...) 772 case tlwire.True: 773 b = append(b, "true"...) 774 case tlwire.Nil: 775 b = append(b, "<nil>"...) 776 case tlwire.Undefined: 777 b = append(b, "<undef>"...) 778 case tlwire.None: 779 // none 780 case tlwire.Hidden: 781 b = append(b, "<hidden>"...) 782 case tlwire.SelfRef: 783 b = append(b, "<self_ref>"...) 784 case tlwire.Float64, tlwire.Float32, tlwire.Float8: 785 var f float64 786 f, i = w.d.Float(p, st) 787 788 if w.FloatFormat != "" { 789 b = hfmt.Appendf(b, w.FloatFormat, f) 790 } else { 791 b = strconv.AppendFloat(b, f, w.FloatChar, w.FloatPrecision, 64) 792 } 793 default: 794 panic(sub) 795 } 796 case tlwire.Semantic: 797 switch sub { 798 case tlwire.Time: 799 var t time.Time 800 t, i = w.d.Time(p, st) 801 802 if w.TimeFormat == "" { 803 b = strconv.AppendInt(b, t.UnixNano(), 10) 804 break 805 } 806 807 if w.TimeLocation != nil { 808 t = t.In(w.TimeLocation) 809 } 810 811 b = t.AppendFormat(b, w.TimeFormat) 812 case tlwire.Duration: 813 var d time.Duration 814 d, i = w.d.Duration(p, st) 815 816 switch { 817 case w.DurationFormat != "" && w.DurationDiv != 0: 818 b = hfmt.Appendf(b, w.DurationFormat, float64(d/w.DurationDiv)) 819 case w.DurationFormat != "": 820 b = hfmt.Appendf(b, w.DurationFormat, d) 821 default: 822 b = w.AppendDuration(b, d) 823 } 824 case WireID: 825 var id ID 826 i = id.TlogParse(p, st) 827 828 st := len(b) 829 b = append(b, "123456789_123456789_123456789_12"[:w.IDWidth]...) 830 id.FormatTo(b[st:], 'v') 831 case tlwire.Hex: 832 b, i = w.ConvertValue(b, p, i, ff|cfHex) 833 case tlwire.Caller: 834 var pc loc.PC 835 var pcs loc.PCs 836 837 pc, pcs, i = w.d.Callers(p, st) 838 839 if pcs == nil { 840 b = hfmt.Appendf(b, w.CallerFormat, pc) 841 break 842 } 843 844 b = append(b, '[') 845 for i, pc := range pcs { 846 if i != 0 { 847 b = append(b, ' ') 848 } 849 850 b = hfmt.Appendf(b, w.CallerFormat, pc) 851 } 852 b = append(b, ']') 853 default: 854 b, i = w.ConvertValue(b, p, i, ff) 855 } 856 default: 857 panic(tag) 858 } 859 860 return b, i 861 } 862 863 func (w *ConsoleWriter) AppendDuration(b []byte, d time.Duration) []byte { 864 if d == 0 { 865 return append(b, ' ', ' ', '0', 's') 866 } 867 868 var buf [32]byte 869 870 if d >= 99*time.Second { 871 const MaxGroups = 2 872 group := 0 873 i := 0 874 875 if d < 0 { 876 d = -d 877 b = append(b, '-') 878 } 879 880 add := func(d, unit time.Duration, suff byte) time.Duration { 881 if group == 0 && d < unit && unit > time.Second || group >= MaxGroups { 882 return d 883 } 884 885 x := int(d / unit) 886 d = d % unit 887 group++ 888 889 if group == MaxGroups && d >= unit/2 { 890 x++ 891 } 892 893 w := width(x) 894 i += w 895 for j := 1; j <= w; j++ { 896 buf[i-j] = byte(x%10) + '0' 897 x /= 10 898 } 899 900 buf[i] = suff 901 i++ 902 903 return d 904 } 905 906 d = add(d, 24*time.Hour, 'd') 907 d = add(d, time.Hour, 'h') 908 d = add(d, time.Minute, 'm') 909 d = add(d, time.Second, 's') 910 911 return append(b, buf[:i]...) 912 } 913 914 neg := d < 0 915 if neg { 916 d = -d 917 } 918 919 end := len(buf) - 4 920 i := end 921 922 for d != 0 { 923 i-- 924 buf[i] = byte(d%10) + '0' 925 d /= 10 926 } 927 928 buf[i-1] = '0' // leading zero for possible carry 929 930 j := i + 3 931 buf[j] += 5 // round 932 933 for j >= i-1 && buf[j] > '9' { // move carry 934 buf[j] = '0' 935 j-- 936 buf[j]++ 937 } 938 939 j = end 940 u := -1 941 942 for buf[j-2] != 0 || buf[j-1] == '1' { // find suitable units 943 j -= 3 944 u++ 945 } 946 947 i = j // beginning 948 949 for buf[j] == '0' || buf[j] == 0 { // leading spaces 950 buf[j] = ' ' 951 j++ 952 } 953 954 digit := j 955 956 j += 3 957 958 // insert point 959 if j-1 > i+3 { 960 buf[j-1] = buf[j-2] 961 } 962 if j-2 > i+3 { 963 buf[j-2] = buf[j-3] 964 } 965 966 buf[i+3] = '.' 967 968 if j > end { 969 j = end 970 } 971 972 for j > i+3 && (buf[j-1] == '0' || buf[j-1] == '.') { // trailing zeros 973 j-- 974 } 975 976 suff := []string{"ns", "µs", "ms", "s", "m"} 977 j += copy(buf[j:], suff[u]) 978 979 if neg { 980 buf[digit-1] = '-' 981 982 if digit == i { 983 i-- 984 } 985 } 986 987 return append(b, buf[i:j]...) 988 } 989 990 func width(n int) (w int) { 991 q := 10 992 w = 1 993 994 for q <= n { 995 w++ 996 q *= 10 997 } 998 999 return w 1000 } 1001 1002 func Color(c ...int) (r []byte) { 1003 if len(c) == 0 { 1004 return nil 1005 } 1006 1007 r = append(r, '\x1b', '[') 1008 1009 for i, c := range c { 1010 if i != 0 { 1011 r = append(r, ';') 1012 } 1013 1014 switch { 1015 case c < 10: 1016 r = append(r, '0'+byte(c%10)) 1017 case c < 100: 1018 r = append(r, '0'+byte(c/10), '0'+byte(c%10)) 1019 default: 1020 r = append(r, '0'+byte(c/100), '0'+byte(c/10%10), '0'+byte(c%10)) 1021 } 1022 } 1023 1024 r = append(r, 'm') 1025 1026 return r 1027 } 1028 1029 func common(x, y []byte) (n int) { 1030 for n < len(y) && x[n] == y[n] { 1031 n++ 1032 } 1033 1034 return 1035 } 1036 1037 func noescapeByteWriter(b *[]byte) *low.Buf { 1038 // return (*low.Buf)(b) 1039 return (*low.Buf)(noescape(unsafe.Pointer(b))) 1040 }