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