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