dumper.go

  1package ast
  2
  3import (
  4	"bytes"
  5	"fmt"
  6	"reflect"
  7	"strconv"
  8	"strings"
  9)
 10
 11// Dump turns ast into a stable string format for assertions in tests
 12func Dump(i interface{}) string {
 13	v := reflect.ValueOf(i)
 14
 15	d := dumper{Buffer: &bytes.Buffer{}}
 16	d.dump(v)
 17
 18	return d.String()
 19}
 20
 21type dumper struct {
 22	*bytes.Buffer
 23	indent int
 24}
 25
 26type Dumpable interface {
 27	Dump() string
 28}
 29
 30func (d *dumper) dump(v reflect.Value) {
 31	if dumpable, isDumpable := v.Interface().(Dumpable); isDumpable {
 32		d.WriteString(dumpable.Dump())
 33		return
 34	}
 35	switch v.Kind() {
 36	case reflect.Bool:
 37		if v.Bool() {
 38			d.WriteString("true")
 39		} else {
 40			d.WriteString("false")
 41		}
 42	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
 43		d.WriteString(fmt.Sprintf("%d", v.Int()))
 44
 45	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
 46		d.WriteString(fmt.Sprintf("%d", v.Uint()))
 47
 48	case reflect.Float32, reflect.Float64:
 49		d.WriteString(fmt.Sprintf("%.2f", v.Float()))
 50
 51	case reflect.String:
 52		if v.Type().Name() != "string" {
 53			d.WriteString(v.Type().Name() + "(" + strconv.Quote(v.String()) + ")")
 54		} else {
 55			d.WriteString(strconv.Quote(v.String()))
 56		}
 57
 58	case reflect.Array, reflect.Slice:
 59		d.dumpArray(v)
 60
 61	case reflect.Interface, reflect.Ptr:
 62		d.dumpPtr(v)
 63
 64	case reflect.Struct:
 65		d.dumpStruct(v)
 66
 67	default:
 68		panic(fmt.Errorf("unsupported kind: %s\n buf: %s", v.Kind().String(), d.String()))
 69	}
 70}
 71
 72func (d *dumper) writeIndent() {
 73	d.Buffer.WriteString(strings.Repeat("  ", d.indent))
 74}
 75
 76func (d *dumper) nl() {
 77	d.Buffer.WriteByte('\n')
 78	d.writeIndent()
 79}
 80
 81func typeName(t reflect.Type) string {
 82	if t.Kind() == reflect.Ptr {
 83		return typeName(t.Elem())
 84	}
 85	return t.Name()
 86}
 87
 88func (d *dumper) dumpArray(v reflect.Value) {
 89	d.WriteString("[" + typeName(v.Type().Elem()) + "]")
 90
 91	for i := 0; i < v.Len(); i++ {
 92		d.nl()
 93		d.WriteString("- ")
 94		d.indent++
 95		d.dump(v.Index(i))
 96		d.indent--
 97	}
 98}
 99
100func (d *dumper) dumpStruct(v reflect.Value) {
101	d.WriteString("<" + v.Type().Name() + ">")
102	d.indent++
103
104	typ := v.Type()
105	for i := 0; i < v.NumField(); i++ {
106		f := v.Field(i)
107		if typ.Field(i).Tag.Get("dump") == "-" {
108			continue
109		}
110
111		if isZero(f) {
112			continue
113		}
114		d.nl()
115		d.WriteString(typ.Field(i).Name)
116		d.WriteString(": ")
117		d.dump(v.Field(i))
118	}
119
120	d.indent--
121}
122
123func isZero(v reflect.Value) bool {
124	switch v.Kind() {
125	case reflect.Ptr, reflect.Interface:
126		return v.IsNil()
127	case reflect.Func, reflect.Map:
128		return v.IsNil()
129
130	case reflect.Array, reflect.Slice:
131		if v.IsNil() {
132			return true
133		}
134		z := true
135		for i := 0; i < v.Len(); i++ {
136			z = z && isZero(v.Index(i))
137		}
138		return z
139	case reflect.Struct:
140		z := true
141		for i := 0; i < v.NumField(); i++ {
142			z = z && isZero(v.Field(i))
143		}
144		return z
145	case reflect.String:
146		return v.String() == ""
147	}
148
149	// Compare other types directly:
150	return reflect.DeepEqual(v.Interface(), reflect.Zero(v.Type()))
151}
152
153func (d *dumper) dumpPtr(v reflect.Value) {
154	if v.IsNil() {
155		d.WriteString("nil")
156		return
157	}
158	d.dump(v.Elem())
159}