1package litter 2 3import ( 4 "bytes" 5 "fmt" 6 "io" 7 "os" 8 "reflect" 9 "regexp" 10 "runtime" 11 "sort" 12 "strconv" 13 "strings" 14) 15 16var ( 17 packageNameStripperRegexp = regexp.MustCompile(`\b[a-zA-Z_]+[a-zA-Z_0-9]+\.`) 18 compactTypeRegexp = regexp.MustCompile(`\s*([,;{}()])\s*`) 19) 20 21// Dumper is the interface for implementing custom dumper for your types. 22type Dumper interface { 23 LitterDump(w io.Writer) 24} 25 26// Options represents configuration options for litter 27type Options struct { 28 Compact bool 29 StripPackageNames bool 30 HidePrivateFields bool 31 HideZeroValues bool 32 FieldExclusions *regexp.Regexp 33 FieldFilter func(reflect.StructField, reflect.Value) bool 34 HomePackage string 35 Separator string 36 StrictGo bool 37 DumpFunc func(reflect.Value, io.Writer) bool 38 39 // DisablePointerReplacement, if true, disables the replacing of pointer data with variable names 40 // when it's safe. This is useful for diffing two structures, where pointer variables would cause 41 // false changes. However, circular graphs are still detected and elided to avoid infinite output. 42 DisablePointerReplacement bool 43} 44 45// Config is the default config used when calling Dump 46var Config = Options{ 47 StripPackageNames: false, 48 HidePrivateFields: true, 49 FieldExclusions: regexp.MustCompile(`^(XXX_.*)$`), // XXX_ is a prefix of fields generated by protoc-gen-go 50 Separator: " ", 51} 52 53type dumpState struct { 54 w io.Writer 55 depth int 56 config *Options 57 pointers ptrmap 58 visitedPointers ptrmap 59 parentPointers ptrmap 60 currentPointer *ptrinfo 61 homePackageRegexp *regexp.Regexp 62} 63 64func (s *dumpState) write(b []byte) { 65 if _, err := s.w.Write(b); err != nil { 66 panic(err) 67 } 68} 69 70func (s *dumpState) writeString(str string) { 71 s.write([]byte(str)) 72} 73 74func (s *dumpState) indent() { 75 if !s.config.Compact { 76 s.write(bytes.Repeat([]byte(" "), s.depth)) 77 } 78} 79 80func (s *dumpState) newlineWithPointerNameComment() { 81 if ptr := s.currentPointer; ptr != nil { 82 if s.config.Compact { 83 s.write([]byte(fmt.Sprintf("/*%s*/", ptr.label()))) 84 } else { 85 s.write([]byte(fmt.Sprintf(" // %s\n", ptr.label()))) 86 } 87 s.currentPointer = nil 88 return 89 } 90 if !s.config.Compact { 91 s.write([]byte("\n")) 92 } 93} 94 95func (s *dumpState) dumpType(v reflect.Value) { 96 typeName := v.Type().String() 97 if s.config.StripPackageNames { 98 typeName = packageNameStripperRegexp.ReplaceAllLiteralString(typeName, "") 99 } else if s.homePackageRegexp != nil { 100 typeName = s.homePackageRegexp.ReplaceAllLiteralString(typeName, "") 101 } 102 if s.config.Compact { 103 typeName = compactTypeRegexp.ReplaceAllString(typeName, "$1") 104 } 105 s.write([]byte(typeName)) 106} 107 108func (s *dumpState) dumpSlice(v reflect.Value) { 109 s.dumpType(v) 110 numEntries := v.Len() 111 if numEntries == 0 { 112 s.write([]byte("{}")) 113 return 114 } 115 s.write([]byte("{")) 116 s.newlineWithPointerNameComment() 117 s.depth++ 118 for i := 0; i < numEntries; i++ { 119 s.indent() 120 s.dumpVal(v.Index(i)) 121 if !s.config.Compact || i < numEntries-1 { 122 s.write([]byte(",")) 123 } 124 s.newlineWithPointerNameComment() 125 } 126 s.depth-- 127 s.indent() 128 s.write([]byte("}")) 129} 130 131func (s *dumpState) dumpStruct(v reflect.Value) { 132 dumpPreamble := func() { 133 s.dumpType(v) 134 s.write([]byte("{")) 135 s.newlineWithPointerNameComment() 136 s.depth++ 137 } 138 preambleDumped := false 139 vt := v.Type() 140 numFields := v.NumField() 141 for i := 0; i < numFields; i++ { 142 vtf := vt.Field(i) 143 if s.config.HidePrivateFields && vtf.PkgPath != "" || s.config.FieldExclusions != nil && s.config.FieldExclusions.MatchString(vtf.Name) { 144 continue 145 } 146 if s.config.FieldFilter != nil && !s.config.FieldFilter(vtf, v.Field(i)) { 147 continue 148 } 149 if s.config.HideZeroValues && isZeroValue(v.Field(i)) { 150 continue 151 } 152 if !preambleDumped { 153 dumpPreamble() 154 preambleDumped = true 155 } 156 s.indent() 157 s.write([]byte(vtf.Name)) 158 if s.config.Compact { 159 s.write([]byte(":")) 160 } else { 161 s.write([]byte(": ")) 162 } 163 s.dumpVal(v.Field(i)) 164 if !s.config.Compact || i < numFields-1 { 165 s.write([]byte(",")) 166 } 167 s.newlineWithPointerNameComment() 168 } 169 if preambleDumped { 170 s.depth-- 171 s.indent() 172 s.write([]byte("}")) 173 } else { 174 // There were no fields dumped 175 s.dumpType(v) 176 s.write([]byte("{}")) 177 } 178} 179 180func (s *dumpState) dumpMap(v reflect.Value) { 181 if v.IsNil() { 182 s.dumpType(v) 183 s.writeString("(nil)") 184 return 185 } 186 187 s.dumpType(v) 188 189 keys := v.MapKeys() 190 if len(keys) == 0 { 191 s.write([]byte("{}")) 192 return 193 } 194 195 s.write([]byte("{")) 196 s.newlineWithPointerNameComment() 197 s.depth++ 198 sort.Sort(mapKeySorter{ 199 keys: keys, 200 options: s.config, 201 }) 202 numKeys := len(keys) 203 for i, key := range keys { 204 s.indent() 205 s.dumpVal(key) 206 if s.config.Compact { 207 s.write([]byte(":")) 208 } else { 209 s.write([]byte(": ")) 210 } 211 s.dumpVal(v.MapIndex(key)) 212 if !s.config.Compact || i < numKeys-1 { 213 s.write([]byte(",")) 214 } 215 s.newlineWithPointerNameComment() 216 } 217 s.depth-- 218 s.indent() 219 s.write([]byte("}")) 220} 221 222func (s *dumpState) dumpFunc(v reflect.Value) { 223 parts := strings.Split(runtime.FuncForPC(v.Pointer()).Name(), "/") 224 name := parts[len(parts)-1] 225 226 // Anonymous function 227 if strings.Count(name, ".") > 1 { 228 s.dumpType(v) 229 } else { 230 if s.config.StripPackageNames { 231 name = packageNameStripperRegexp.ReplaceAllLiteralString(name, "") 232 } else if s.homePackageRegexp != nil { 233 name = s.homePackageRegexp.ReplaceAllLiteralString(name, "") 234 } 235 if s.config.Compact { 236 name = compactTypeRegexp.ReplaceAllString(name, "$1") 237 } 238 s.write([]byte(name)) 239 } 240} 241 242func (s *dumpState) dumpCustom(v reflect.Value, buf *bytes.Buffer) { 243 244 // Dump the type 245 s.dumpType(v) 246 247 if s.config.Compact { 248 s.write(buf.Bytes()) 249 return 250 } 251 252 // Now output the dump taking care to apply the current indentation-level 253 // and pointer name comments. 254 var err error 255 firstLine := true 256 for err == nil { 257 var lineBytes []byte 258 lineBytes, err = buf.ReadBytes('\n') 259 line := strings.TrimRight(string(lineBytes), " \n") 260 261 if err != nil && err != io.EOF { 262 break 263 } 264 // Do not indent first line 265 if firstLine { 266 firstLine = false 267 } else { 268 s.indent() 269 } 270 s.write([]byte(line)) 271 272 // At EOF we're done 273 if err == io.EOF { 274 return 275 } 276 s.newlineWithPointerNameComment() 277 } 278 panic(err) 279} 280 281func (s *dumpState) dump(value interface{}) { 282 if value == nil { 283 printNil(s.w) 284 return 285 } 286 v := reflect.ValueOf(value) 287 s.dumpVal(v) 288} 289 290func (s *dumpState) descendIntoPossiblePointer(value reflect.Value, f func()) { 291 canonicalize := true 292 if isPointerValue(value) { 293 // If elision disabled, and this is not a circular reference, don't canonicalize 294 if s.config.DisablePointerReplacement && s.parentPointers.add(value) { 295 canonicalize = false 296 } 297 298 // Add to stack of pointers we're recursively descending into 299 s.parentPointers.add(value) 300 defer s.parentPointers.remove(value) 301 } 302 303 if !canonicalize { 304 ptr, _ := s.pointerFor(value) 305 s.currentPointer = ptr 306 f() 307 return 308 } 309 310 ptr, firstVisit := s.pointerFor(value) 311 if ptr == nil { 312 f() 313 return 314 } 315 if firstVisit { 316 s.currentPointer = ptr 317 f() 318 return 319 } 320 s.write([]byte(ptr.label())) 321} 322 323func (s *dumpState) dumpVal(value reflect.Value) { 324 if value.Kind() == reflect.Ptr && value.IsNil() { 325 s.write([]byte("nil")) 326 return 327 } 328 329 v := deInterface(value) 330 kind := v.Kind() 331 332 // Try to handle with dump func 333 if s.config.DumpFunc != nil { 334 buf := new(bytes.Buffer) 335 if s.config.DumpFunc(v, buf) { 336 s.dumpCustom(v, buf) 337 return 338 } 339 } 340 341 // Handle custom dumpers 342 dumperType := reflect.TypeOf((*Dumper)(nil)).Elem() 343 if v.Type().Implements(dumperType) { 344 s.descendIntoPossiblePointer(v, func() { 345 // Run the custom dumper buffering the output 346 buf := new(bytes.Buffer) 347 dumpFunc := v.MethodByName("LitterDump") 348 dumpFunc.Call([]reflect.Value{reflect.ValueOf(buf)}) 349 s.dumpCustom(v, buf) 350 }) 351 return 352 } 353 354 switch kind { 355 case reflect.Invalid: 356 // Do nothing. We should never get here since invalid has already 357 // been handled above. 358 s.write([]byte("<invalid>")) 359 360 case reflect.Bool: 361 printBool(s.w, v.Bool()) 362 363 case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: 364 printInt(s.w, v.Int(), 10) 365 366 case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: 367 printUint(s.w, v.Uint(), 10) 368 369 case reflect.Float32: 370 printFloat(s.w, v.Float(), 32) 371 372 case reflect.Float64: 373 printFloat(s.w, v.Float(), 64) 374 375 case reflect.Complex64: 376 printComplex(s.w, v.Complex(), 32) 377 378 case reflect.Complex128: 379 printComplex(s.w, v.Complex(), 64) 380 381 case reflect.String: 382 s.write([]byte(strconv.Quote(v.String()))) 383 384 case reflect.Slice: 385 if v.IsNil() { 386 printNil(s.w) 387 break 388 } 389 fallthrough 390 391 case reflect.Array: 392 s.descendIntoPossiblePointer(v, func() { 393 s.dumpSlice(v) 394 }) 395 396 case reflect.Interface: 397 // The only time we should get here is for nil interfaces due to 398 // unpackValue calls. 399 if v.IsNil() { 400 printNil(s.w) 401 } 402 403 case reflect.Ptr: 404 s.descendIntoPossiblePointer(v, func() { 405 if s.config.StrictGo { 406 s.writeString(fmt.Sprintf("(func(v %s) *%s { return &v })(", v.Elem().Type(), v.Elem().Type())) 407 s.dumpVal(v.Elem()) 408 s.writeString(")") 409 } else { 410 s.writeString("&") 411 s.dumpVal(v.Elem()) 412 } 413 }) 414 415 case reflect.Map: 416 s.descendIntoPossiblePointer(v, func() { 417 s.dumpMap(v) 418 }) 419 420 case reflect.Struct: 421 s.dumpStruct(v) 422 423 case reflect.Func: 424 s.dumpFunc(v) 425 426 default: 427 if v.CanInterface() { 428 s.writeString(fmt.Sprintf("%v", v.Interface())) 429 } else { 430 s.writeString(fmt.Sprintf("%v", v.String())) 431 } 432 } 433} 434 435// registers that the value has been visited and checks to see if it is one of the 436// pointers we will see multiple times. If it is, it returns a temporary name for this 437// pointer. It also returns a boolean value indicating whether this is the first time 438// this name is returned so the caller can decide whether the contents of the pointer 439// has been dumped before or not. 440func (s *dumpState) pointerFor(v reflect.Value) (*ptrinfo, bool) { 441 if isPointerValue(v) { 442 if info, ok := s.pointers.get(v); ok { 443 firstVisit := s.visitedPointers.add(v) 444 return info, firstVisit 445 } 446 } 447 return nil, false 448} 449 450// prepares a new state object for dumping the provided value 451func newDumpState(value interface{}, options *Options, writer io.Writer) *dumpState { 452 result := &dumpState{ 453 config: options, 454 pointers: mapReusedPointers(reflect.ValueOf(value)), 455 w: writer, 456 } 457 458 if options.HomePackage != "" { 459 result.homePackageRegexp = regexp.MustCompile(fmt.Sprintf("\\b%s\\.", options.HomePackage)) 460 } 461 462 return result 463} 464 465// Dump a value to stdout 466func Dump(value ...interface{}) { 467 (&Config).Dump(value...) 468} 469 470// Sdump dumps a value to a string 471func Sdump(value ...interface{}) string { 472 return (&Config).Sdump(value...) 473} 474 475// Dump a value to stdout according to the options 476func (o Options) Dump(values ...interface{}) { 477 for i, value := range values { 478 state := newDumpState(value, &o, os.Stdout) 479 if i > 0 { 480 state.write([]byte(o.Separator)) 481 } 482 state.dump(value) 483 } 484 _, _ = os.Stdout.Write([]byte("\n")) 485} 486 487// Sdump dumps a value to a string according to the options 488func (o Options) Sdump(values ...interface{}) string { 489 buf := new(bytes.Buffer) 490 for i, value := range values { 491 if i > 0 { 492 _, _ = buf.Write([]byte(o.Separator)) 493 } 494 state := newDumpState(value, &o, buf) 495 state.dump(value) 496 } 497 return buf.String() 498} 499 500type mapKeySorter struct { 501 keys []reflect.Value 502 options *Options 503} 504 505func (s mapKeySorter) Len() int { 506 return len(s.keys) 507} 508 509func (s mapKeySorter) Swap(i, j int) { 510 s.keys[i], s.keys[j] = s.keys[j], s.keys[i] 511} 512 513func (s mapKeySorter) Less(i, j int) bool { 514 ibuf := new(bytes.Buffer) 515 jbuf := new(bytes.Buffer) 516 newDumpState(s.keys[i], s.options, ibuf).dumpVal(s.keys[i]) 517 newDumpState(s.keys[j], s.options, jbuf).dumpVal(s.keys[j]) 518 return ibuf.String() < jbuf.String() 519} 520