github.com/dvln/pretty@v0.0.0-20161024040402-00a5f9316993/formatter.go (about) 1 package pretty 2 3 import ( 4 "fmt" 5 "io" 6 "reflect" 7 "strconv" 8 "strings" 9 "text/tabwriter" 10 "unicode" 11 12 "github.com/dvln/text" 13 ) 14 15 // outputIndentLevel is covered in the SetOutputIndentLevel() func header 16 var outputIndentLevel = 4 17 18 // humanize is covered in the SetHumanize() function header 19 var humanize = false 20 21 // outputPrefixStr is covered in the SetOutputPrefixStr() function header 22 var outputPrefixStr = "" 23 24 // newlineAfterItems allows one to make the humanize output insert a blank 25 // line between entries in a somewhat sensical way... normally that is off. 26 var newlineAfterItems = false 27 28 // sawCloseBracketLast is incremented when a json-like '}' is seen in 29 // the output and then set back to 0 when anything else is seen (if one 30 // has 2 or 3 '}' chars in a row it'll increment til a non '}' char is 31 // seen... could be used to add spacing between items 32 var sawCloseBracketLast = 0 33 34 // currOutputLine only kicks on in 'humanize' active (set to true) mode, it 35 // examines all output being dumped and tracks what is on the current line 36 // of output and will clear that line when \n goes across the output. This 37 // is used to decide if a carriage return + indent is needed when in 38 // human friendly output mode (if we see a ':' in the current line of output 39 // it means a "<key>:" header has been printed and a newline/indent is needed 40 // for the multi-line data to follow) 41 var currOutputLine = "" 42 43 type formatter struct { 44 v reflect.Value 45 force bool 46 quote bool 47 } 48 49 // Formatter makes a wrapper, f, that will format x as go source with line 50 // breaks and tabs. Object f responds to the "%v" formatting verb when both the 51 // "#" and " " (space) flags are set, for example: 52 // 53 // fmt.Sprintf("%# v", Formatter(x)) 54 // 55 // If one of these two flags is not set, or any other verb is used, f will 56 // format x according to the usual rules of package fmt. 57 // In particular, if x satisfies fmt.Formatter, then x.Format will be called. 58 func Formatter(x interface{}) (f fmt.Formatter) { 59 return formatter{v: reflect.ValueOf(x), quote: true} 60 } 61 62 func (fo formatter) String() string { 63 return fmt.Sprint(fo.v.Interface()) // unwrap it 64 } 65 66 func (fo formatter) passThrough(f fmt.State, c rune) { 67 s := "%" 68 for i := 0; i < 128; i++ { 69 if f.Flag(i) { 70 s += string(i) 71 } 72 } 73 if w, ok := f.Width(); ok { 74 s += fmt.Sprintf("%d", w) 75 } 76 if p, ok := f.Precision(); ok { 77 s += fmt.Sprintf(".%d", p) 78 } 79 s += string(c) 80 fmt.Fprintf(f, s, fo.v.Interface()) 81 } 82 83 // OutputIndentLevel returns the current step-wise indent that will 84 // be used when dumping output via pretty (defaults to 4 to start), 85 // see SetOutputIndentLevel() to adjust. 86 func OutputIndentLevel() int { 87 return outputIndentLevel 88 } 89 90 // SetOutputIndentLevel can be used to adjust the step-wise indent for 91 // structure representations that are printed. Give an integer number 92 // of spaces (recommended 2 or 4, default is 4 to start) 93 func SetOutputIndentLevel(indent int) { 94 outputIndentLevel = indent 95 } 96 97 // Humanize will return the current true/false state of if "humanizing" of 98 // the output is active or not. By default it starts off and you get what 99 // 'pretty' was originally set up for, a go-like structure w/details. See 100 // SetHumanize() to flip it on (and see what it does). 101 func Humanize() bool { 102 return humanize 103 } 104 105 // SetHumanize can be used to flip on a more "Humanistic" form of output 106 // from the 'pretty' package (by default this is false and the regular 107 // 'pretty' package output that looks more like Go structures). What 108 // "humanize" means is that the output could be used as the readable text 109 // output to your users. When in humanize form 'pretty:' tags (like 'json:..' 110 // tags on structs) are honored for overriding field names (spaces ok) and 111 // supporting a form of omitempty (omitting nil intefaces/ptrs, empty arrays 112 // or maps, etc... but does not suppress output for false or 0 int's now). 113 // Anyhow, build a structure for your output, put json and pretty tags in 114 // the structure and then dump output easily in JSON (via json marshal) or 115 // dump the same struct to human friendly text for users. 116 func SetHumanize(b bool) { 117 humanize = b 118 } 119 120 // NewlineAfterItems will return the current true/false state of if newlines 121 // after each item is desired or not. By default this is off. 122 func NewlineAfterItems() bool { 123 return newlineAfterItems 124 } 125 126 // SetNewlineAfterItems can be used to adjust humanistic output format so 127 // that there's an empty line between items that are printed. By default 128 // it's off but this can be used to flip that on by setting to true. 129 func SetNewlineAfterItems(b bool) { 130 newlineAfterItems = b 131 } 132 133 // OutputPrefixStr returns the current overall text prefix string, see 134 // the SetOutputPrefixStr() routine to set it. 135 func OutputPrefixStr() string { 136 return outputPrefixStr 137 } 138 139 // SetOutputPrefixStr sets the output prefix string to the given string 140 func SetOutputPrefixStr(s string) { 141 outputPrefixStr = s 142 } 143 144 func (fo formatter) Format(f fmt.State, c rune) { 145 if fo.force || c == 'v' && f.Flag('#') && f.Flag(' ') { 146 w := tabwriter.NewWriter(f, outputIndentLevel, outputIndentLevel, 1, ' ', 0) 147 p := &printer{tw: w, Writer: w, visited: make(map[visit]int)} 148 p.printValue(fo.v, true, fo.quote) 149 w.Flush() 150 return 151 } 152 fo.passThrough(f, c) 153 } 154 155 type printer struct { 156 io.Writer 157 tw *tabwriter.Writer 158 visited map[visit]int 159 depth int 160 } 161 162 func (p *printer) indent() *printer { 163 q := *p 164 q.tw = tabwriter.NewWriter(p.Writer, outputIndentLevel, outputIndentLevel, 1, ' ', 0) 165 q.Writer = text.NewIndentWriter(q.tw, []byte{'\t'}) 166 return &q 167 } 168 169 func (p *printer) printInline(v reflect.Value, x interface{}, showType bool) { 170 if showType && !humanize { 171 writeString(p, v.Type().String()) 172 fmt.Fprintf(p, "(%#v)", x) 173 } else { 174 result := fmt.Sprintf("%#v", x) 175 if humanize && result != "" && strings.TrimSpace(result) == "" { 176 fmt.Fprintf(p, "\"%s\"", result) 177 } else { 178 fmt.Fprintf(p, "%s", result) 179 } 180 if humanize { 181 lines := strings.Split(result, "\n") 182 currOutputLine = lines[len(lines)-1] 183 } 184 } 185 } 186 187 // tagOptions is the string following a comma in a struct field's "json" 188 // tag, or the empty string. It does not include the leading comma. 189 type tagOptions string 190 191 // parseTag splits a struct field's json tag into its name and 192 // comma-separated options. 193 func parseTag(tag string) (string, tagOptions) { 194 if idx := strings.Index(tag, ","); idx != -1 { 195 return tag[:idx], tagOptions(tag[idx+1:]) 196 } 197 return tag, tagOptions("") 198 } 199 200 // Contains reports whether a comma-separated list of options 201 // contains a particular substr flag. substr must be surrounded by a 202 // string boundary or commas. 203 func (o tagOptions) Contains(optionName string) bool { 204 if len(o) == 0 { 205 return false 206 } 207 s := string(o) 208 for s != "" { 209 var next string 210 i := strings.Index(s, ",") 211 if i >= 0 { 212 s, next = s[:i], s[i+1:] 213 } 214 if s == optionName { 215 return true 216 } 217 s = next 218 } 219 return false 220 } 221 222 // isValidTag is borrowed from Go's encoding/json 223 func isValidTag(s string) bool { 224 if s == "" { 225 return false 226 } 227 for _, c := range s { 228 switch { 229 case strings.ContainsRune("!#$%&()*+-./:<=>?@[]^_{|}~ ", c): 230 // Backslash and quote chars are reserved, but 231 // otherwise any punctuation chars are allowed 232 // in a tag name. 233 default: 234 if !unicode.IsLetter(c) && !unicode.IsDigit(c) { 235 return false 236 } 237 } 238 } 239 return true 240 } 241 242 // isEmptyValue determines for "humanistic" output if we want to see a given 243 // type or not... different than JSON in that we typically do want to see 244 // true or false settings, and even 0 values for various numerical types... 245 func isEmptyValue(v reflect.Value) bool { 246 switch v.Kind() { 247 case reflect.Array, reflect.Map, reflect.Slice, reflect.String: 248 return v.Len() == 0 249 case reflect.Interface, reflect.Ptr: 250 return v.IsNil() 251 } 252 return false 253 } 254 255 // indentNeeded is called when there is an open bracket for a new structure 256 // or map or array to be printed, normally we always need to toss in a 257 // carriage return and indent *but* if we're doing humanized output we 258 // don't show the {}'s nor do we do the newlines', we want the items 259 // to appear at the very left margin and show cleanly from there 260 func indentNeeded() bool { 261 if !humanize { 262 return true 263 } 264 if strings.ContainsRune(currOutputLine, ':') { 265 return true 266 } 267 return false 268 } 269 270 // newlineNeeded should only be used within the context of the humanized 271 // output mode (see callers). It's basically deciding when a newline 272 // needs to be dumped ... since in humanized mode we're not dumping 273 // the '{' and '}' characters at all (on their own lines in normal 'pretty' 274 // pkg output) we need to be smarter about when newlines need to be 275 // printed in such situations. This type of 'pretty' output: 276 // { (normal pretty output prints the type before open bracket, which is "[]interface{}" 277 // { (this ones type is a hash with one entry with value a struct) 278 // key1: { 279 // structfield1: value 280 // structfieldtwo: value 281 // } 282 // }, 283 // { 284 // key2: { 285 // structfield1: value 286 // structfieldtwo: value 287 // } 288 // }, 289 // } 290 // In humanized output form this comes out as: 291 // key1: 292 // structfield1: value 293 // structfieldtwo: value 294 // key2: 295 // structfield1: value 296 // structfieldtwo: value 297 // .. 298 // So all those newlines after the close brackets aren't needed, see 299 // indentNeeded() above as that handles the opening brackets and indent. 300 // Note that some folks may want a blank line between each entry and 301 // that can be done by counting the close brackets 302 func newlineNeeded() bool { 303 if sawCloseBracketLast == 0 || (newlineAfterItems && sawCloseBracketLast == 2) { 304 return true 305 } 306 return false 307 } 308 309 // printValue must keep track of already-printed pointer values to avoid 310 // infinite recursion. 311 type visit struct { 312 v uintptr 313 typ reflect.Type 314 } 315 316 func (p *printer) printValue(v reflect.Value, showType, quote bool) { 317 if p.depth > 10 { 318 writeString(p, "!%v(DEPTH EXCEEDED)") 319 return 320 } 321 322 var expand bool 323 324 if humanize { 325 quote = false 326 showType = false 327 expand = true 328 } 329 switch v.Kind() { 330 case reflect.Bool: 331 p.printInline(v, v.Bool(), showType) 332 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 333 p.printInline(v, v.Int(), showType) 334 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 335 p.printInline(v, v.Uint(), showType) 336 case reflect.Float32, reflect.Float64: 337 p.printInline(v, v.Float(), showType) 338 case reflect.Complex64, reflect.Complex128: 339 fmt.Fprintf(p, "%#v", v.Complex()) 340 case reflect.String: 341 p.fmtString(v.String(), quote) 342 case reflect.Map: 343 t := v.Type() 344 if showType { 345 if !humanize { 346 writeString(p, t.String()) 347 } 348 } 349 writeByte(p, '{') // '}' to balance the char 350 if nonzero(v) || humanize { 351 expand = !canInline(v.Type()) 352 pp := p 353 if expand { 354 if indentNeeded() { 355 writeByte(p, '\n') 356 pp = p.indent() 357 } 358 } 359 keys := v.MapKeys() 360 for i := 0; i < v.Len(); i++ { 361 showTypeInStruct := true 362 if humanize { 363 showTypeInStruct = false 364 } 365 k := keys[i] 366 mv := v.MapIndex(k) 367 pp.printValue(k, false, true) 368 writeByte(pp, ':') 369 if expand { 370 writeByte(pp, '\t') 371 } 372 if !humanize { 373 showTypeInStruct = t.Elem().Kind() == reflect.Interface 374 } 375 pp.printValue(mv, showTypeInStruct, true) 376 if expand { 377 if humanize { 378 if newlineNeeded() { 379 writeString(pp, "\n") 380 } 381 } else { 382 writeString(pp, ",\n") 383 } 384 } else if i < v.Len()-1 { 385 writeString(pp, ", ") 386 } 387 } 388 if expand { 389 pp.tw.Flush() 390 } 391 } 392 // '{' to balance below line 393 writeByte(p, '}') 394 case reflect.Struct: 395 t := v.Type() 396 if v.CanAddr() { 397 addr := v.UnsafeAddr() 398 vis := visit{addr, t} 399 if vd, ok := p.visited[vis]; ok && vd < p.depth { 400 p.fmtString(t.String()+"{(CYCLIC REFERENCE)}", false) 401 break // don't print v again 402 } 403 p.visited[vis] = p.depth 404 } 405 406 if showType { 407 if !humanize { 408 writeString(p, t.String()) 409 } 410 } 411 writeByte(p, '{') // '}' to balance the char 412 if nonzero(v) || humanize { 413 expand = !canInline(v.Type()) 414 pp := p 415 if expand { 416 if indentNeeded() { 417 writeByte(p, '\n') 418 pp = p.indent() 419 } 420 } 421 for i := 0; i < v.NumField(); i++ { 422 showTypeInStruct := true 423 if humanize { 424 showTypeInStruct = false 425 } 426 if f := t.Field(i); f.Name != "" { 427 name := f.Name 428 omitEmpty := false 429 if humanize { 430 tag := f.Tag.Get("pretty") 431 if tag == "-" { 432 continue 433 } 434 newName, opts := parseTag(tag) 435 if isValidTag(newName) { 436 name = newName 437 } 438 omitEmpty = opts.Contains("omitempty") 439 val := getField(v, i) 440 if omitEmpty && isEmptyValue(val) { 441 continue 442 } 443 } 444 writeString(pp, name) 445 writeByte(pp, ':') 446 if expand { 447 writeByte(pp, '\t') 448 } 449 if !humanize { 450 showTypeInStruct = labelType(f.Type) 451 } 452 } 453 pp.printValue(getField(v, i), showTypeInStruct, true) 454 if humanize { 455 if newlineNeeded() { 456 writeByte(pp, '\n') 457 } 458 } else if expand { 459 writeString(pp, ",\n") 460 } else if i < v.NumField()-1 { 461 writeString(pp, ", ") 462 } 463 } 464 if expand { 465 pp.tw.Flush() 466 } 467 } 468 // '{' to balance below line 469 writeByte(p, '}') 470 case reflect.Interface: 471 switch e := v.Elem(); { 472 case e.Kind() == reflect.Invalid: 473 writeString(p, "nil") 474 case e.IsValid(): 475 pp := *p 476 pp.depth++ 477 pp.printValue(e, showType, true) 478 default: 479 writeString(p, v.Type().String()) 480 writeString(p, "(nil)") 481 } 482 case reflect.Array, reflect.Slice: 483 t := v.Type() 484 if showType { 485 writeString(p, t.String()) 486 } 487 if v.Kind() == reflect.Slice && v.IsNil() && showType { 488 writeString(p, "(nil)") 489 break 490 } 491 if v.Kind() == reflect.Slice && v.IsNil() { 492 writeString(p, "nil") 493 break 494 } 495 writeByte(p, '{') // '}' to balance the char 496 expand = !canInline(v.Type()) 497 pp := p 498 if expand { 499 if indentNeeded() { 500 writeByte(p, '\n') 501 pp = p.indent() 502 } 503 } 504 for i := 0; i < v.Len(); i++ { 505 showTypeInSlice := t.Elem().Kind() == reflect.Interface 506 pp.printValue(v.Index(i), showTypeInSlice, true) 507 if humanize { 508 if newlineNeeded() { 509 writeByte(pp, '\n') 510 } 511 } else if expand { 512 writeString(pp, ",\n") 513 } else if i < v.Len()-1 { 514 writeString(pp, ", ") 515 } 516 } 517 if expand { 518 pp.tw.Flush() 519 } 520 // '{' to balance below line 521 writeByte(p, '}') 522 case reflect.Ptr: 523 e := v.Elem() 524 if !e.IsValid() { 525 if humanize { 526 writeString(p, "nil") 527 } else { 528 writeByte(p, '(') 529 writeString(p, v.Type().String()) 530 writeString(p, ")(nil)") 531 } 532 } else { 533 pp := *p 534 pp.depth++ 535 if !humanize { 536 writeByte(pp, '&') 537 } 538 pp.printValue(e, true, true) 539 } 540 case reflect.Chan: 541 x := v.Pointer() 542 if showType { 543 writeByte(p, '(') 544 writeString(p, v.Type().String()) 545 fmt.Fprintf(p, ")(%#v)", x) 546 } else { 547 fmt.Fprintf(p, "%#v", x) 548 } 549 case reflect.Func: 550 writeString(p, v.Type().String()) 551 writeString(p, " {...}") 552 case reflect.UnsafePointer: 553 p.printInline(v, v.Pointer(), showType) 554 case reflect.Invalid: 555 writeString(p, "nil") 556 } 557 } 558 559 func canInline(t reflect.Type) bool { 560 if humanize { 561 return false 562 } 563 switch t.Kind() { 564 case reflect.Map: 565 return !canExpand(t.Elem()) 566 case reflect.Struct: 567 for i := 0; i < t.NumField(); i++ { 568 if canExpand(t.Field(i).Type) { 569 return false 570 } 571 } 572 return true 573 case reflect.Interface: 574 return false 575 case reflect.Array, reflect.Slice: 576 return !canExpand(t.Elem()) 577 case reflect.Ptr: 578 return false 579 case reflect.Chan, reflect.Func, reflect.UnsafePointer: 580 return false 581 } 582 return true 583 } 584 585 func canExpand(t reflect.Type) bool { 586 switch t.Kind() { 587 case reflect.Map, reflect.Struct, 588 reflect.Interface, reflect.Array, reflect.Slice, 589 reflect.Ptr: 590 return true 591 } 592 return false 593 } 594 595 func labelType(t reflect.Type) bool { 596 switch t.Kind() { 597 case reflect.Interface, reflect.Struct: 598 return true 599 } 600 return false 601 } 602 603 func (p *printer) fmtString(s string, quote bool) { 604 if quote || (humanize && s != "" && strings.TrimSpace(s) == "") { 605 s = strconv.Quote(s) 606 } 607 writeString(p, s) 608 } 609 610 func writeByte(w io.Writer, b byte) { 611 // if "humanized" output don't print struct/array format chars '{' and '}' 612 // which are currently always done via writeByte only, sweet 613 if humanize && (b == '{' || b == '}') { 614 // '{' to balance below line, fixes dumb editor bracket matching 615 if b == '}' { 616 sawCloseBracketLast++ 617 } 618 return 619 } 620 if humanize { 621 if b == '\n' { 622 currOutputLine = "" 623 } else { 624 currOutputLine = currOutputLine + string(b) 625 } 626 } 627 sawCloseBracketLast = 0 628 w.Write([]byte{b}) 629 } 630 631 func writeString(w io.Writer, s string) { 632 if humanize { 633 // all close brackets (for fmt'ing) use writeByte() so zero it out 634 if s != "" { 635 sawCloseBracketLast = 0 636 } 637 // in case multi-line string, split it on newline, store curr last line 638 lines := strings.Split(s, "\n") 639 currOutputLine = lines[len(lines)-1] 640 } 641 io.WriteString(w, s) 642 } 643 644 func getField(v reflect.Value, i int) reflect.Value { 645 val := v.Field(i) 646 if val.Kind() == reflect.Interface && !val.IsNil() { 647 val = val.Elem() 648 } 649 return val 650 }