dump.go 12 KB

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