| package pretty |
| |
| import ( |
| "fmt" |
| "io" |
| "reflect" |
| "strconv" |
| "text/tabwriter" |
| |
| "github.com/kr/text" |
| "github.com/rogpeppe/go-internal/fmtsort" |
| ) |
| |
| type formatter struct { |
| v reflect.Value |
| force bool |
| quote bool |
| } |
| |
| // Formatter makes a wrapper, f, that will format x as go source with line |
| // breaks and tabs. Object f responds to the "%v" formatting verb when both the |
| // "#" and " " (space) flags are set, for example: |
| // |
| // fmt.Sprintf("%# v", Formatter(x)) |
| // |
| // If one of these two flags is not set, or any other verb is used, f will |
| // format x according to the usual rules of package fmt. |
| // In particular, if x satisfies fmt.Formatter, then x.Format will be called. |
| func Formatter(x interface{}) (f fmt.Formatter) { |
| return formatter{v: reflect.ValueOf(x), quote: true} |
| } |
| |
| func (fo formatter) String() string { |
| return fmt.Sprint(fo.v.Interface()) // unwrap it |
| } |
| |
| func (fo formatter) passThrough(f fmt.State, c rune) { |
| s := "%" |
| for i := 0; i < 128; i++ { |
| if f.Flag(i) { |
| s += string(rune(i)) |
| } |
| } |
| if w, ok := f.Width(); ok { |
| s += fmt.Sprintf("%d", w) |
| } |
| if p, ok := f.Precision(); ok { |
| s += fmt.Sprintf(".%d", p) |
| } |
| s += string(c) |
| fmt.Fprintf(f, s, fo.v.Interface()) |
| } |
| |
| func (fo formatter) Format(f fmt.State, c rune) { |
| if fo.force || c == 'v' && f.Flag('#') && f.Flag(' ') { |
| w := tabwriter.NewWriter(f, 4, 4, 1, ' ', 0) |
| p := &printer{tw: w, Writer: w, visited: make(map[visit]int)} |
| p.printValue(fo.v, true, fo.quote) |
| w.Flush() |
| return |
| } |
| fo.passThrough(f, c) |
| } |
| |
| type printer struct { |
| io.Writer |
| tw *tabwriter.Writer |
| visited map[visit]int |
| depth int |
| } |
| |
| func (p *printer) indent() *printer { |
| q := *p |
| q.tw = tabwriter.NewWriter(p.Writer, 4, 4, 1, ' ', 0) |
| q.Writer = text.NewIndentWriter(q.tw, []byte{'\t'}) |
| return &q |
| } |
| |
| func (p *printer) printInline(v reflect.Value, x interface{}, showType bool) { |
| if showType { |
| io.WriteString(p, v.Type().String()) |
| fmt.Fprintf(p, "(%#v)", x) |
| } else { |
| fmt.Fprintf(p, "%#v", x) |
| } |
| } |
| |
| // printValue must keep track of already-printed pointer values to avoid |
| // infinite recursion. |
| type visit struct { |
| v uintptr |
| typ reflect.Type |
| } |
| |
| func (p *printer) printValue(v reflect.Value, showType, quote bool) { |
| if p.depth > 10 { |
| io.WriteString(p, "!%v(DEPTH EXCEEDED)") |
| return |
| } |
| |
| if v.IsValid() && v.CanInterface() { |
| i := v.Interface() |
| if goStringer, ok := i.(fmt.GoStringer); ok { |
| io.WriteString(p, goStringer.GoString()) |
| return |
| } |
| } |
| |
| switch v.Kind() { |
| case reflect.Bool: |
| p.printInline(v, v.Bool(), showType) |
| case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |
| p.printInline(v, v.Int(), showType) |
| case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: |
| p.printInline(v, v.Uint(), showType) |
| case reflect.Float32, reflect.Float64: |
| p.printInline(v, v.Float(), showType) |
| case reflect.Complex64, reflect.Complex128: |
| fmt.Fprintf(p, "%#v", v.Complex()) |
| case reflect.String: |
| p.fmtString(v.String(), quote) |
| case reflect.Map: |
| t := v.Type() |
| if showType { |
| io.WriteString(p, t.String()) |
| } |
| writeByte(p, '{') |
| if nonzero(v) { |
| expand := !canInline(v.Type()) |
| pp := p |
| if expand { |
| writeByte(p, '\n') |
| pp = p.indent() |
| } |
| sm := fmtsort.Sort(v) |
| for i := 0; i < v.Len(); i++ { |
| k := sm.Key[i] |
| mv := sm.Value[i] |
| pp.printValue(k, false, true) |
| writeByte(pp, ':') |
| if expand { |
| writeByte(pp, '\t') |
| } |
| showTypeInStruct := t.Elem().Kind() == reflect.Interface |
| pp.printValue(mv, showTypeInStruct, true) |
| if expand { |
| io.WriteString(pp, ",\n") |
| } else if i < v.Len()-1 { |
| io.WriteString(pp, ", ") |
| } |
| } |
| if expand { |
| pp.tw.Flush() |
| } |
| } |
| writeByte(p, '}') |
| case reflect.Struct: |
| t := v.Type() |
| if v.CanAddr() { |
| addr := v.UnsafeAddr() |
| vis := visit{addr, t} |
| if vd, ok := p.visited[vis]; ok && vd < p.depth { |
| p.fmtString(t.String()+"{(CYCLIC REFERENCE)}", false) |
| break // don't print v again |
| } |
| p.visited[vis] = p.depth |
| } |
| |
| if showType { |
| io.WriteString(p, t.String()) |
| } |
| writeByte(p, '{') |
| if nonzero(v) { |
| expand := !canInline(v.Type()) |
| pp := p |
| if expand { |
| writeByte(p, '\n') |
| pp = p.indent() |
| } |
| for i := 0; i < v.NumField(); i++ { |
| showTypeInStruct := true |
| if f := t.Field(i); f.Name != "" { |
| io.WriteString(pp, f.Name) |
| writeByte(pp, ':') |
| if expand { |
| writeByte(pp, '\t') |
| } |
| showTypeInStruct = labelType(f.Type) |
| } |
| pp.printValue(getField(v, i), showTypeInStruct, true) |
| if expand { |
| io.WriteString(pp, ",\n") |
| } else if i < v.NumField()-1 { |
| io.WriteString(pp, ", ") |
| } |
| } |
| if expand { |
| pp.tw.Flush() |
| } |
| } |
| writeByte(p, '}') |
| case reflect.Interface: |
| switch e := v.Elem(); { |
| case e.Kind() == reflect.Invalid: |
| io.WriteString(p, "nil") |
| case e.IsValid(): |
| pp := *p |
| pp.depth++ |
| pp.printValue(e, showType, true) |
| default: |
| io.WriteString(p, v.Type().String()) |
| io.WriteString(p, "(nil)") |
| } |
| case reflect.Array, reflect.Slice: |
| t := v.Type() |
| if showType { |
| io.WriteString(p, t.String()) |
| } |
| if v.Kind() == reflect.Slice && v.IsNil() && showType { |
| io.WriteString(p, "(nil)") |
| break |
| } |
| if v.Kind() == reflect.Slice && v.IsNil() { |
| io.WriteString(p, "nil") |
| break |
| } |
| writeByte(p, '{') |
| expand := !canInline(v.Type()) |
| pp := p |
| if expand { |
| writeByte(p, '\n') |
| pp = p.indent() |
| } |
| for i := 0; i < v.Len(); i++ { |
| showTypeInSlice := t.Elem().Kind() == reflect.Interface |
| pp.printValue(v.Index(i), showTypeInSlice, true) |
| if expand { |
| io.WriteString(pp, ",\n") |
| } else if i < v.Len()-1 { |
| io.WriteString(pp, ", ") |
| } |
| } |
| if expand { |
| pp.tw.Flush() |
| } |
| writeByte(p, '}') |
| case reflect.Ptr: |
| e := v.Elem() |
| if !e.IsValid() { |
| writeByte(p, '(') |
| io.WriteString(p, v.Type().String()) |
| io.WriteString(p, ")(nil)") |
| } else { |
| pp := *p |
| pp.depth++ |
| writeByte(pp, '&') |
| pp.printValue(e, true, true) |
| } |
| case reflect.Chan: |
| x := v.Pointer() |
| if showType { |
| writeByte(p, '(') |
| io.WriteString(p, v.Type().String()) |
| fmt.Fprintf(p, ")(%#v)", x) |
| } else { |
| fmt.Fprintf(p, "%#v", x) |
| } |
| case reflect.Func: |
| io.WriteString(p, v.Type().String()) |
| io.WriteString(p, " {...}") |
| case reflect.UnsafePointer: |
| p.printInline(v, v.Pointer(), showType) |
| case reflect.Invalid: |
| io.WriteString(p, "nil") |
| } |
| } |
| |
| func canInline(t reflect.Type) bool { |
| switch t.Kind() { |
| case reflect.Map: |
| return !canExpand(t.Elem()) |
| case reflect.Struct: |
| for i := 0; i < t.NumField(); i++ { |
| if canExpand(t.Field(i).Type) { |
| return false |
| } |
| } |
| return true |
| case reflect.Interface: |
| return false |
| case reflect.Array, reflect.Slice: |
| return !canExpand(t.Elem()) |
| case reflect.Ptr: |
| return false |
| case reflect.Chan, reflect.Func, reflect.UnsafePointer: |
| return false |
| } |
| return true |
| } |
| |
| func canExpand(t reflect.Type) bool { |
| switch t.Kind() { |
| case reflect.Map, reflect.Struct, |
| reflect.Interface, reflect.Array, reflect.Slice, |
| reflect.Ptr: |
| return true |
| } |
| return false |
| } |
| |
| func labelType(t reflect.Type) bool { |
| switch t.Kind() { |
| case reflect.Interface, reflect.Struct: |
| return true |
| } |
| return false |
| } |
| |
| func (p *printer) fmtString(s string, quote bool) { |
| if quote { |
| s = strconv.Quote(s) |
| } |
| io.WriteString(p, s) |
| } |
| |
| func writeByte(w io.Writer, b byte) { |
| w.Write([]byte{b}) |
| } |
| |
| func getField(v reflect.Value, i int) reflect.Value { |
| val := v.Field(i) |
| if val.Kind() == reflect.Interface && !val.IsNil() { |
| val = val.Elem() |
| } |
| return val |
| } |