package goon import ( "bytes" "fmt" "go/format" "io" "reflect" "strconv" "strings" "time" "github.com/shurcooL/go/reflectsource" ) var config = struct { indent string }{ indent: "\t", } // dumpState contains information about the state of a dump operation. type dumpState struct { w io.Writer depth int pointers map[uintptr]int ignoreNextType bool ignoreNextIndent bool } // indent performs indentation according to the depth level and cs.Indent // option. func (d *dumpState) indent() { if d.ignoreNextIndent { d.ignoreNextIndent = false return } d.w.Write(bytes.Repeat([]byte(config.indent), d.depth)) } // unpackValue returns values inside of non-nil interfaces when possible. // This is useful for data types like structs, arrays, slices, and maps which // can contain varying types packed inside an interface. func (d *dumpState) unpackValue(v reflect.Value) reflect.Value { if v.Kind() == reflect.Interface && !v.IsNil() { v = v.Elem() } return v } // dumpPtr handles formatting of pointers by indirecting them as necessary. func (d *dumpState) dumpPtr(v reflect.Value) { // Remove pointers at or below the current depth from map used to detect // circular refs. for k, depth := range d.pointers { if depth >= d.depth { delete(d.pointers, k) } } // Figure out how many levels of indirection there are by dereferencing // pointers and unpacking interfaces down the chain while detecting circular // references. nilFound := false cycleFound := false indirects := 0 ve := v for ve.Kind() == reflect.Ptr { if ve.IsNil() { nilFound = true break } indirects++ addr := ve.Pointer() if pd, ok := d.pointers[addr]; ok && pd < d.depth { cycleFound = true indirects-- break } d.pointers[addr] = d.depth ve = ve.Elem() if ve.Kind() == reflect.Interface { if ve.IsNil() { nilFound = true break } ve = ve.Elem() } } // Display type information. d.w.Write(bytes.Repeat(ampersandBytes, indirects)) // Display dereferenced value. switch { case nilFound: d.w.Write(nilBytes) case cycleFound: d.w.Write(circularBytes) default: d.ignoreNextType = true d.dump(ve) } } // dump is the main workhorse for dumping a value. It uses the passed reflect // value to figure out what kind of object we are dealing with and formats it // appropriately. It is a recursive function, however circular data structures // are detected and handled properly. func (d *dumpState) dump(v reflect.Value) { // Handle invalid reflect values immediately. kind := v.Kind() if kind == reflect.Invalid { d.w.Write(invalidAngleBytes) return } // Handle pointers specially. if kind == reflect.Ptr { d.indent() d.w.Write(openParenBytes) d.w.Write([]byte(typeStringWithoutPackagePrefix(v))) d.w.Write(closeParenBytes) d.w.Write(openParenBytes) d.dumpPtr(v) d.w.Write(closeParenBytes) return } // Print type information unless already handled elsewhere. var shouldPrintClosingBr = false if !d.ignoreNextType { d.indent() d.w.Write(openParenBytes) d.w.Write([]byte(typeStringWithoutPackagePrefix(v))) d.w.Write(closeParenBytes) d.w.Write(openParenBytes) shouldPrintClosingBr = true } d.ignoreNextType = false if v.Type() == timeType { t := v.Interface().(time.Time) switch t.IsZero() { case false: var location string switch t.Location() { case time.UTC: location = "time.UTC" case time.Local: location = "time.Local" default: location = fmt.Sprintf("must(time.LoadLocation(%q))", t.Location().String()) } fmt.Fprintf(d.w, "time.Date(%d, %d, %d, %d, %d, %d, %d, %s)", t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), location) case true: d.w.Write([]byte("time.Time{}")) } goto AfterKindSwitch } switch kind { case reflect.Invalid: // Do nothing. We should never get here since invalid has already // been handled above. case reflect.Bool: printBool(d.w, v.Bool()) case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: printInt(d.w, v.Int(), 10) case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: printUint(d.w, v.Uint(), 10) case reflect.Float32: printFloat(d.w, v.Float(), 32) case reflect.Float64: printFloat(d.w, v.Float(), 64) case reflect.Complex64: printComplex(d.w, v.Complex(), 32) case reflect.Complex128: printComplex(d.w, v.Complex(), 64) case reflect.Array: d.w.Write([]byte(typeStringWithoutPackagePrefix(v))) d.w.Write(openBraceNewlineBytes) d.depth++ for i := 0; i < v.Len(); i++ { d.dump(d.unpackValue(v.Index(i))) d.w.Write(commaNewlineBytes) } d.depth-- d.indent() d.w.Write(closeBraceBytes) case reflect.Slice: if v.IsNil() { d.w.Write(nilBytes) } else { d.w.Write([]byte(typeStringWithoutPackagePrefix(v))) d.w.Write(openBraceNewlineBytes) d.depth++ for i := 0; i < v.Len(); i++ { d.dump(d.unpackValue(v.Index(i))) d.w.Write(commaNewlineBytes) } d.depth-- d.indent() d.w.Write(closeBraceBytes) } case reflect.String: d.w.Write([]byte(strconv.Quote(v.String()))) case reflect.Interface: // If we got here, it's because interface is nil // See https://github.com/davecgh/go-spew/issues/12 d.w.Write(nilBytes) case reflect.Ptr: // Do nothing. We should never get here since pointers have already // been handled above. case reflect.Map: if v.IsNil() { d.w.Write(nilBytes) } else { d.w.Write([]byte(typeStringWithoutPackagePrefix(v))) d.w.Write(openBraceNewlineBytes) d.depth++ keys := v.MapKeys() for _, key := range keys { d.dump(d.unpackValue(key)) d.w.Write(colonSpaceBytes) d.ignoreNextIndent = true d.dump(d.unpackValue(v.MapIndex(key))) d.w.Write(commaNewlineBytes) } d.depth-- d.indent() d.w.Write(closeBraceBytes) } case reflect.Struct: d.w.Write([]byte(typeStringWithoutPackagePrefix(v))) d.w.Write(openBraceBytes) d.depth++ { vt := v.Type() numFields := v.NumField() if numFields > 0 { d.w.Write(newlineBytes) } for i := 0; i < numFields; i++ { d.indent() vtf := vt.Field(i) d.w.Write([]byte(vtf.Name)) d.w.Write(colonSpaceBytes) d.ignoreNextIndent = true d.dump(d.unpackValue(v.Field(i))) d.w.Write(commaBytes) d.w.Write(newlineBytes) } } d.depth-- d.indent() d.w.Write(closeBraceBytes) case reflect.Uintptr: printHexPtr(d.w, uintptr(v.Uint())) case reflect.Func: d.w.Write([]byte(reflectsource.GetFuncValueSourceAsString(v))) case reflect.UnsafePointer, reflect.Chan: printHexPtr(d.w, v.Pointer()) // There were not any other types at the time this code was written, but // fall back to letting the default fmt package handle it in case any new // types are added. default: if v.CanInterface() { fmt.Fprintf(d.w, "%v", v.Interface()) } else { fmt.Fprintf(d.w, "%v", v.String()) } } AfterKindSwitch: if shouldPrintClosingBr { d.w.Write(closeParenBytes) } } var timeType = reflect.TypeOf(time.Time{}) func typeStringWithoutPackagePrefix(v reflect.Value) string { //return v.Type().String()[len(v.Type().PkgPath())+1:] // TODO: Error checking? //return v.Type().PkgPath() //return v.Type().String() //return v.Type().Name() /*x := v.Type().String() if strings.HasPrefix(x, "main.") { x = x[len("main."):] } return x*/ px := v.Type().String() prefix := px[0 : len(px)-len(strings.TrimLeft(px, "*"))] // Split "**main.Lang" -> "**" and "main.Lang" x := px[len(prefix):] x = strings.TrimPrefix(x, "main.") x = strings.TrimPrefix(x, "goon_test.") return prefix + x /*x = string(debug.Stack())//GetLine(string(debug.Stack()), 0) //x = x[1:strings.Index(x, ":")] //spew.Printf(">%s<\n", x) //panic(nil) //st := string(debug.Stack()) //debug.PrintStack() return x*/ } // fdump is a helper function to consolidate the logic from the various public // methods which take varying writers and config states. func fdump(w io.Writer, a ...interface{}) { for _, arg := range a { d := dumpState{w: w} if arg == nil { d.w.Write(interfaceBytes) d.w.Write(nilParenBytes) } else { d.pointers = make(map[uintptr]int) d.dump(reflect.ValueOf(arg)) } d.w.Write(newlineBytes) } } // bdump dumps to []byte. func bdump(a ...interface{}) []byte { var buf bytes.Buffer fdump(&buf, a...) return gofmt(buf.Bytes()) } func fdumpNamed(w io.Writer, names []string, a ...interface{}) { for argIndex, arg := range a { d := dumpState{w: w} if argIndex < len(names) { d.w.Write([]byte(names[argIndex])) d.w.Write([]byte(" = ")) } if arg == nil { d.w.Write(interfaceBytes) d.w.Write(nilParenBytes) } else { d.pointers = make(map[uintptr]int) d.dump(reflect.ValueOf(arg)) } if len(names) >= len(a) { d.w.Write(newlineBytes) } else { if argIndex < len(a)-1 { d.w.Write(commaNewlineBytes) } else { d.w.Write(newlineBytes) } } } } func bdumpNamed(names []string, a ...interface{}) []byte { var buf bytes.Buffer fdumpNamed(&buf, names, a...) return gofmt(buf.Bytes()) } func gofmt(src []byte) []byte { formattedSrc, err := format.Source(src) if nil != err { return []byte("gofmt error (" + err.Error() + ")!\n" + string(src)) } return formattedSrc }