pax_global_header00006660000000000000000000000064135251110540014507gustar00rootroot0000000000000052 comment=6d3310f9743f04a36bd5c898067ecb80ac3b332b litter-1.2.0/000077500000000000000000000000001352511105400130125ustar00rootroot00000000000000litter-1.2.0/.gitignore000066400000000000000000000000121352511105400147730ustar00rootroot00000000000000.DS_Store litter-1.2.0/.travis.yml000066400000000000000000000000311352511105400151150ustar00rootroot00000000000000language: go go: - 1.9.x litter-1.2.0/CHANGELOG.md000066400000000000000000000003101352511105400146150ustar00rootroot00000000000000# 1.1.0 (2017-11-1) A slight breaking change. The dump-method of the `Dumper` interface has changed from `Dump` to `LitterDump` to mitigate potential collisions. # 1.0.0 (2017-10-29) Tagged 1.0.0. litter-1.2.0/LICENSE000066400000000000000000000020721352511105400140200ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2016-2017 Sanity.io. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. litter-1.2.0/README.md000066400000000000000000000110441352511105400142710ustar00rootroot00000000000000[![!Build Status](https://travis-ci.org/sanity-io/litter.svg?branch=master)](https://travis-ci.org/sanity-io/litter) # Litter **Litter is a pretty printer library for Go data structures to aid in debugging and testing.** --- Litter is provided by
Sanity: The Headless CMS Construction Kit
--- Litter named for the fact that it outputs *literals*, which you *litter* your output with. As a side benefit, all Litter output is syntactically correct Go. You can use Litter to emit data during debug, and it's also really nice for "snapshot data" in unit tests, since it produces consistent, sorted output. Litter was inspired by [Spew](https://github.com/davecgh/go-spew), but focuses on terseness and readability. ### Basic example This: ```go type Person struct { Name string Age int Parent *Person } litter.Dump(Person{ Name: "Bob", Age: 20, Parent: &Person{ Name: "Jane", Age: 50, }, }) ``` will output: ``` Person{ Name: "Bob", Age: 20, Parent: &Person{ Name: "Jane", Age: 50, }, } ``` ### Use in tests Litter is a great alternative to JSON or YAML for providing "snapshots" or example data. For example: ```go func TestSearch(t *testing.T) { result := DoSearch() actual := litterOpts.Sdump(result) expected, err := ioutil.ReadFile("testdata.txt") if err != nil { // First run, write test data since it doesn't exist if !os.IsNotExist(err) { t.Error(err) } ioutil.Write("testdata.txt", actual, 0644) actual = expected } if expected != actual { t.Errorf("Expected %s, got %s", expected, actual) } } ``` The first run will use Litter to write the data to `testdata.txt`. On subsequent runs, the test will compare the data. Since Litter always provides a consistent view of a value, you can compare the strings directly. ### Circular references Litter detects circular references or aliasing, and will replace additional references to the same object with aliases. For example: ```go type Circular struct { Self *Circular } selfref := Circular{} selfref.Self = &selfref litter.Dump(selfref) ``` will output: ``` Circular { // p0 Self: p0, } ``` ## Installation ```bash $ go get -u github.com/sanity-io/litter ``` ## Quick start Add this import line to the file you're working in: ```go import "github.com/sanity-io/litter" ``` To dump a variable with full newlines, indentation, type, and aliasing information, use `Dump` or `Sdump`: ```go litter.Dump(myVar1) str := litter.Sdump(myVar1) ``` ### `litter.Dump(value, ...)` Dumps the data structure to STDOUT. ### `litter.Sdump(value, ...)` Returns the dump as a string ## Configuration You can configure litter globally by modifying the default `litter.Config` ```go // Strip all package names from types litter.Config.StripPackageNames = true // Hide private struct fields from dumped structs litter.Config.HidePrivateFields = true // Hide fields matched with given regexp if it is not nil. It is set up to hide fields generate with protoc-gen-go litter.Config.FieldExclusions = regexp.MustCompile(`^(XXX_.*)$`) // Sets a "home" package. The package name will be stripped from all its types litter.Config.HomePackage = "mypackage" // Sets separator used when multiple arguments are passed to Dump() or Sdump(). litter.Config.Separator = "\n" // Use compact output: strip newlines and other unnecessary whitespace litter.Config.Compact = true ``` ### `litter.Options` Allows you to configure a local configuration of litter to allow for proper compartmentalization of state at the expense of some comfort: ``` go sq := litter.Options { HidePrivateFields: true, HomePackage: "thispack", Separator: " ", } sq.Dump("dumped", "with", "local", "settings") ``` ## Custom dumpers Implement the interface Dumper on your types to take control of how your type is dumped. ``` go type Dumper interface { LitterDump(w io.Writer) } ``` Just write your custom dump to the provided stream, using multiple lines divided by `"\n"` if you need. Litter might indent your output according to context, and optionally decorate your first line with a pointer comment where appropriate. A couple of examples from the test suite: ``` go type CustomMultiLineDumper struct {} func (cmld *CustomMultiLineDumper) LitterDump(w io.Writer) { w.Write([]byte("{\n multi\n line\n}")) } type CustomSingleLineDumper int func (csld CustomSingleLineDumper) LitterDump(w io.Writer) { w.Write([]byte("")) } ```` litter-1.2.0/dump.go000066400000000000000000000254141352511105400143140ustar00rootroot00000000000000package litter import ( "bytes" "fmt" "io" "os" "reflect" "regexp" "runtime" "sort" "strconv" "strings" ) var packageNameStripperRegexp = regexp.MustCompile("\\b[a-zA-Z_]+[a-zA-Z_0-9]+\\.") var compactTypeRegexp = regexp.MustCompile(`\s*([,;{}()])\s*`) // Dumper is the interface for implementing custom dumper for your types. type Dumper interface { LitterDump(w io.Writer) } // Options represents configuration options for litter type Options struct { Compact bool StripPackageNames bool HidePrivateFields bool HideZeroValues bool FieldExclusions *regexp.Regexp FieldFilter func(reflect.StructField, reflect.Value) bool HomePackage string Separator string } // Config is the default config used when calling Dump var Config = Options{ StripPackageNames: false, HidePrivateFields: true, FieldExclusions: regexp.MustCompile(`^(XXX_.*)$`), // XXX_ is a prefix of fields generated by protoc-gen-go Separator: " ", } type dumpState struct { w io.Writer depth int config *Options pointers []uintptr visitedPointers []uintptr currentPointerName string homePackageRegexp *regexp.Regexp } func (s *dumpState) indent() { if !s.config.Compact { s.w.Write(bytes.Repeat([]byte(" "), s.depth)) } } func (s *dumpState) newlineWithPointerNameComment() { if s.currentPointerName != "" { if s.config.Compact { s.w.Write([]byte(fmt.Sprintf("/*%s*/", s.currentPointerName))) } else { s.w.Write([]byte(fmt.Sprintf(" // %s\n", s.currentPointerName))) } s.currentPointerName = "" return } if !s.config.Compact { s.w.Write([]byte("\n")) } } func (s *dumpState) dumpType(v reflect.Value) { typeName := v.Type().String() if s.config.StripPackageNames { typeName = packageNameStripperRegexp.ReplaceAllLiteralString(typeName, "") } else if s.homePackageRegexp != nil { typeName = s.homePackageRegexp.ReplaceAllLiteralString(typeName, "") } if s.config.Compact { typeName = compactTypeRegexp.ReplaceAllString(typeName, "$1") } s.w.Write([]byte(typeName)) } func (s *dumpState) dumpSlice(v reflect.Value) { s.dumpType(v) numEntries := v.Len() if numEntries == 0 { s.w.Write([]byte("{}")) if s.config.Compact { s.w.Write([]byte(";")) } s.newlineWithPointerNameComment() return } s.w.Write([]byte("{")) s.newlineWithPointerNameComment() s.depth++ for i := 0; i < numEntries; i++ { s.indent() s.dumpVal(v.Index(i)) if !s.config.Compact || i < numEntries-1 { s.w.Write([]byte(",")) } s.newlineWithPointerNameComment() } s.depth-- s.indent() s.w.Write([]byte("}")) } func (s *dumpState) dumpStruct(v reflect.Value) { dumpPreamble := func() { s.dumpType(v) s.w.Write([]byte("{")) s.newlineWithPointerNameComment() s.depth++ } preambleDumped := false vt := v.Type() numFields := v.NumField() for i := 0; i < numFields; i++ { vtf := vt.Field(i) if s.config.HidePrivateFields && vtf.PkgPath != "" || s.config.FieldExclusions != nil && s.config.FieldExclusions.MatchString(vtf.Name) { continue } if s.config.FieldFilter != nil && !s.config.FieldFilter(vtf, v.Field(i)) { continue } if s.config.HideZeroValues && isZeroValue(v.Field(i)) { continue } if !preambleDumped { dumpPreamble() preambleDumped = true } s.indent() s.w.Write([]byte(vtf.Name)) if s.config.Compact { s.w.Write([]byte(":")) } else { s.w.Write([]byte(": ")) } s.dumpVal(v.Field(i)) if !s.config.Compact || i < numFields-1 { s.w.Write([]byte(",")) } s.newlineWithPointerNameComment() } if preambleDumped { s.depth-- s.indent() s.w.Write([]byte("}")) } else { // There were no fields dumped s.dumpType(v) s.w.Write([]byte("{}")) } } func (s *dumpState) dumpMap(v reflect.Value) { s.dumpType(v) s.w.Write([]byte("{")) s.newlineWithPointerNameComment() s.depth++ keys := v.MapKeys() sort.Sort(mapKeySorter{ keys: keys, options: s.config, }) numKeys := len(keys) for i, key := range keys { s.indent() s.dumpVal(key) if s.config.Compact { s.w.Write([]byte(":")) } else { s.w.Write([]byte(": ")) } s.dumpVal(v.MapIndex(key)) if !s.config.Compact || i < numKeys-1 { s.w.Write([]byte(",")) } s.newlineWithPointerNameComment() } s.depth-- s.indent() s.w.Write([]byte("}")) } func (s *dumpState) dumpFunc(v reflect.Value) { parts := strings.Split(runtime.FuncForPC(v.Pointer()).Name(), "/") name := parts[len(parts)-1] // Anonymous function if strings.Count(name, ".") > 1 { s.dumpType(v) } else { if s.config.StripPackageNames { name = packageNameStripperRegexp.ReplaceAllLiteralString(name, "") } else if s.homePackageRegexp != nil { name = s.homePackageRegexp.ReplaceAllLiteralString(name, "") } if s.config.Compact { name = compactTypeRegexp.ReplaceAllString(name, "$1") } s.w.Write([]byte(name)) } } func (s *dumpState) dumpCustom(v reflect.Value) { // Run the custom dumper buffering the output buf := new(bytes.Buffer) dumpFunc := v.MethodByName("LitterDump") dumpFunc.Call([]reflect.Value{reflect.ValueOf(buf)}) // Dump the type s.dumpType(v) if s.config.Compact { s.w.Write(buf.Bytes()) return } // Now output the dump taking care to apply the current indentation-level // and pointer name comments. var err error firstLine := true for err == nil { var lineBytes []byte lineBytes, err = buf.ReadBytes('\n') line := strings.TrimRight(string(lineBytes), " \n") if err != nil && err != io.EOF { break } // Do not indent first line if firstLine { firstLine = false } else { s.indent() } s.w.Write([]byte(line)) // At EOF we're done if err == io.EOF { return } s.newlineWithPointerNameComment() } panic(err) } func (s *dumpState) dump(value interface{}) { if value == nil { printNil(s.w) return } v := reflect.ValueOf(value) s.dumpVal(v) } func (s *dumpState) handlePointerAliasingAndCheckIfShouldDescend(value reflect.Value) bool { pointerName, firstVisit := s.pointerNameFor(value) if pointerName == "" { return true } if firstVisit { s.currentPointerName = pointerName return true } s.w.Write([]byte(pointerName)) return false } func (s *dumpState) dumpVal(value reflect.Value) { if value.Kind() == reflect.Ptr && value.IsNil() { s.w.Write([]byte("nil")) return } v := deInterface(value) kind := v.Kind() // Handle custom dumpers dumperType := reflect.TypeOf((*Dumper)(nil)).Elem() if v.Type().Implements(dumperType) { if s.handlePointerAliasingAndCheckIfShouldDescend(v) { s.dumpCustom(v) } return } switch kind { case reflect.Invalid: // Do nothing. We should never get here since invalid has already // been handled above. s.w.Write([]byte("")) case reflect.Bool: printBool(s.w, v.Bool()) case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: printInt(s.w, v.Int(), 10) case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: printUint(s.w, v.Uint(), 10) case reflect.Float32: printFloat(s.w, v.Float(), 32) case reflect.Float64: printFloat(s.w, v.Float(), 64) case reflect.Complex64: printComplex(s.w, v.Complex(), 32) case reflect.Complex128: printComplex(s.w, v.Complex(), 64) case reflect.String: s.w.Write([]byte(strconv.Quote(v.String()))) case reflect.Slice: if v.IsNil() { printNil(s.w) break } fallthrough case reflect.Array: if s.handlePointerAliasingAndCheckIfShouldDescend(v) { s.dumpSlice(v) } case reflect.Interface: // The only time we should get here is for nil interfaces due to // unpackValue calls. if v.IsNil() { printNil(s.w) } case reflect.Ptr: if s.handlePointerAliasingAndCheckIfShouldDescend(v) { s.w.Write([]byte("&")) s.dumpVal(v.Elem()) } case reflect.Map: if s.handlePointerAliasingAndCheckIfShouldDescend(v) { s.dumpMap(v) } case reflect.Struct: s.dumpStruct(v) case reflect.Func: s.dumpFunc(v) default: if v.CanInterface() { fmt.Fprintf(s.w, "%v", v.Interface()) } else { fmt.Fprintf(s.w, "%v", v.String()) } } } // call to signal that the pointer is being visited, returns true if this is the // first visit to that pointer. Used to detect when to output the entire contents // behind a pointer (the first time), and when to just emit a name (all other times) func (s *dumpState) visitPointerAndCheckIfItIsTheFirstTime(ptr uintptr) bool { for _, addr := range s.visitedPointers { if addr == ptr { return false } } s.visitedPointers = append(s.visitedPointers, ptr) return true } // registers that the value has been visited and checks to see if it is one of the // pointers we will see multiple times. If it is, it returns a temporary name for this // pointer. It also returns a boolean value indicating whether this is the first time // this name is returned so the caller can decide whether the contents of the pointer // has been dumped before or not. func (s *dumpState) pointerNameFor(v reflect.Value) (string, bool) { if isPointerValue(v) { ptr := v.Pointer() for i, addr := range s.pointers { if ptr == addr { firstVisit := s.visitPointerAndCheckIfItIsTheFirstTime(ptr) return fmt.Sprintf("p%d", i), firstVisit } } } return "", false } // prepares a new state object for dumping the provided value func newDumpState(value interface{}, options *Options, writer io.Writer) *dumpState { result := &dumpState{ config: options, pointers: MapReusedPointers(reflect.ValueOf(value)), w: writer, } if options.HomePackage != "" { result.homePackageRegexp = regexp.MustCompile(fmt.Sprintf("\\b%s\\.", options.HomePackage)) } return result } // Dump a value to stdout func Dump(value ...interface{}) { (&Config).Dump(value...) } // Sdump dumps a value to a string func Sdump(value ...interface{}) string { return (&Config).Sdump(value...) } // Dump a value to stdout according to the options func (o Options) Dump(values ...interface{}) { for i, value := range values { state := newDumpState(value, &o, os.Stdout) if i > 0 { state.w.Write([]byte(o.Separator)) } state.dump(value) } os.Stdout.Write([]byte("\n")) } // Sdump dumps a value to a string according to the options func (o Options) Sdump(values ...interface{}) string { buf := new(bytes.Buffer) for i, value := range values { if i > 0 { buf.Write([]byte(o.Separator)) } state := newDumpState(value, &o, buf) state.dump(value) } return buf.String() } type mapKeySorter struct { keys []reflect.Value options *Options } func (s mapKeySorter) Len() int { return len(s.keys) } func (s mapKeySorter) Swap(i, j int) { s.keys[i], s.keys[j] = s.keys[j], s.keys[i] } func (s mapKeySorter) Less(i, j int) bool { ibuf := new(bytes.Buffer) jbuf := new(bytes.Buffer) newDumpState(s.keys[i], s.options, ibuf).dumpVal(s.keys[i]) newDumpState(s.keys[j], s.options, jbuf).dumpVal(s.keys[j]) return ibuf.String() < jbuf.String() } litter-1.2.0/dump_test.go000066400000000000000000000135151352511105400153520ustar00rootroot00000000000000package litter_test import ( "fmt" "io" "io/ioutil" "os" "os/exec" "reflect" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/sanity-io/litter" ) func Function(arg1 string, arg2 int) (string, error) { return "", nil } type BlankStruct struct{} type BasicStruct struct { Public int private int } type InterfaceStruct struct { Ifc interface{} } type RecursiveStruct struct { Ptr *RecursiveStruct } type CustomMultiLineDumper struct { Dummy int } func (cmld *CustomMultiLineDumper) LitterDump(w io.Writer) { _, _ = w.Write([]byte("{\n multi\n line\n}")) } type CustomSingleLineDumper int func (csld CustomSingleLineDumper) LitterDump(w io.Writer) { _, _ = w.Write([]byte("")) } func TestSdump_primitives(t *testing.T) { runTests(t, "primitives", []interface{}{ false, true, 7, int8(10), int16(10), int32(10), int64(10), uint8(10), uint16(10), uint32(10), uint64(10), uint(10), float32(12.3), float64(12.3), complex64(12 + 10.5i), complex128(-1.2 - 0.1i), "string with \"quote\"", []int{1, 2, 3}, interface{}("hello from interface"), BlankStruct{}, &BlankStruct{}, BasicStruct{1, 2}, Function, func(arg string) (bool, error) { return false, nil }, nil, interface{}(nil), }) } func TestSdump_customDumper(t *testing.T) { cmld := CustomMultiLineDumper{Dummy: 1} cmld2 := CustomMultiLineDumper{Dummy: 2} csld := CustomSingleLineDumper(42) csld2 := CustomSingleLineDumper(43) runTests(t, "customDumper", map[string]interface{}{ "v1": &cmld, "v2": &cmld, "v2x": &cmld2, "v3": csld, "v4": &csld, "v5": &csld, "v6": &csld2, }) } func TestSdump_pointerAliasing(t *testing.T) { p0 := &RecursiveStruct{Ptr: nil} p1 := &RecursiveStruct{Ptr: p0} p2 := &RecursiveStruct{} p2.Ptr = p2 runTests(t, "pointerAliasing", []*RecursiveStruct{ p0, p0, p1, p2, }) } func TestSdump_nilIntefacesInStructs(t *testing.T) { p0 := &InterfaceStruct{nil} p1 := &InterfaceStruct{p0} runTests(t, "nilIntefacesInStructs", []*InterfaceStruct{ p0, p1, p0, nil, }) } func TestSdump_config(t *testing.T) { type options struct { Compact bool StripPackageNames bool HidePrivateFields bool HomePackage string Separator string } opts := options{ StripPackageNames: false, HidePrivateFields: true, Separator: " ", } data := []interface{}{ opts, &BasicStruct{1, 2}, Function, litter.Dump, func(s string, i int) (bool, error) { return false, nil }, } runTestWithCfg(t, "config_Compact", &litter.Options{ Compact: true, }, data) runTestWithCfg(t, "config_HidePrivateFields", &litter.Options{ HidePrivateFields: true, }, data) runTestWithCfg(t, "config_HideZeroValues", &litter.Options{ HideZeroValues: true, }, data) runTestWithCfg(t, "config_StripPackageNames", &litter.Options{ StripPackageNames: true, }, data) runTestWithCfg(t, "config_HomePackage", &litter.Options{ HomePackage: "litter_test", }, data) runTestWithCfg(t, "config_FieldFilter", &litter.Options{ FieldFilter: func(f reflect.StructField, v reflect.Value) bool { return f.Type.Kind() == reflect.String }, }, data) } func TestSdump_multipleArgs(t *testing.T) { value1 := []string{"x", "y"} value2 := int32(42) runTestWithCfg(t, "multipleArgs_noSeparator", &litter.Options{}, value1, value2) runTestWithCfg(t, "multipleArgs_lineBreak", &litter.Options{Separator: "\n"}, value1, value2) runTestWithCfg(t, "multipleArgs_separator", &litter.Options{Separator: "***"}, value1, value2) } func TestSdump_maps(t *testing.T) { runTests(t, "maps", []interface{}{ map[string]string{ "hello": "there", "something": "something something", "another string": "indeed", }, map[int]string{ 3: "three", 1: "one", 2: "two", }, map[int]*BlankStruct{ 2: &BlankStruct{}, }, }) } var standardCfg = litter.Options{} func runTestWithCfg(t *testing.T, name string, cfg *litter.Options, cases ...interface{}) { t.Run(name, func(t *testing.T) { fileName := fmt.Sprintf("testdata/%s.dump", name) dump := cfg.Sdump(cases...) reference, err := ioutil.ReadFile(fileName) if os.IsNotExist(err) { t.Logf("Note: Test data file %s does not exist, writing it; verify contents!", fileName) err := ioutil.WriteFile(fileName, []byte(dump), 0644) if err != nil { t.Error(err) } return } assertEqualStringsWithDiff(t, string(reference), dump) }) } func runTests(t *testing.T, name string, cases ...interface{}) { runTestWithCfg(t, name, &standardCfg, cases...) } func diffStrings(t *testing.T, expected, actual string) (*string, bool) { if actual == expected { return nil, true } dir, err := ioutil.TempDir("", "test") require.NoError(t, err) defer os.RemoveAll(dir) require.NoError(t, ioutil.WriteFile(fmt.Sprintf("%s/expected", dir), []byte(expected), 0644)) require.NoError(t, ioutil.WriteFile(fmt.Sprintf("%s/actual", dir), []byte(actual), 0644)) out, err := exec.Command("diff", "--side-by-side", fmt.Sprintf("%s/expected", dir), fmt.Sprintf("%s/actual", dir)).Output() if _, ok := err.(*exec.ExitError); !ok { require.NoError(t, err) } diff := string(out) return &diff, false } func assertEqualStringsWithDiff(t *testing.T, expected, actual string, msgAndArgs ...interface{}) bool { diff, ok := diffStrings(t, expected, actual) if ok { return true } message := messageFromMsgAndArgs(msgAndArgs...) if message == "" { message = "Strings are different" } assert.Fail(t, fmt.Sprintf("%s (left is expected, right is actual):\n%s", message, *diff)) return false } func messageFromMsgAndArgs(msgAndArgs ...interface{}) string { if len(msgAndArgs) == 0 || msgAndArgs == nil { return "" } if len(msgAndArgs) == 1 { return msgAndArgs[0].(string) } if len(msgAndArgs) > 1 { return fmt.Sprintf(msgAndArgs[0].(string), msgAndArgs[1:]...) } return "" } litter-1.2.0/go.mod000066400000000000000000000004111352511105400141140ustar00rootroot00000000000000module github.com/sanity-io/litter require ( github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b // indirect github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0 // indirect github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312 ) litter-1.2.0/go.sum000066400000000000000000000012611352511105400141450ustar00rootroot00000000000000github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b h1:XxMZvQZtTXpWMNWK82vdjCLCe7uGMFXdTsJH0v3Hkvw= github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0 h1:GD+A8+e+wFkqje55/2fOVnZPkoDIu1VooBWfNrnY8Uo= github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312 h1:UsFdQ3ZmlzS0BqZYGxvYaXvFGUbCmPGy8DM7qWJJiIQ= github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= litter-1.2.0/mapper.go000066400000000000000000000044521352511105400146320ustar00rootroot00000000000000package litter import ( "reflect" "sort" ) type pointerMap struct { pointers []uintptr reusedPointers []uintptr } // MapReusedPointers : Given a structure, it will recurively map all pointers mentioned in the tree, breaking // circular references and provide a list of all pointers that was referenced at least twice // by the provided structure. func MapReusedPointers(v reflect.Value) []uintptr { pm := &pointerMap{ reusedPointers: []uintptr{}, } pm.consider(v) return pm.reusedPointers } // Recursively consider v and each of its children, updating the map according to the // semantics of MapReusedPointers func (pm *pointerMap) consider(v reflect.Value) { if v.Kind() == reflect.Invalid { return } // fmt.Printf("Considering [%s] %#v\n\r", v.Type().String(), v.Interface()) if isPointerValue(v) && v.Pointer() != 0 { // pointer is 0 for unexported fields // fmt.Printf("Ptr is %d\n\r", v.Pointer()) reused := pm.addPointerReturnTrueIfWasReused(v.Pointer()) if reused { // No use descending inside this value, since it have been seen before and all its descendants // have been considered return } } // Now descend into any children of this value switch v.Kind() { case reflect.Slice, reflect.Array: numEntries := v.Len() for i := 0; i < numEntries; i++ { pm.consider(v.Index(i)) } case reflect.Interface: pm.consider(v.Elem()) case reflect.Ptr: pm.consider(v.Elem()) case reflect.Map: keys := v.MapKeys() sort.Sort(mapKeySorter{ keys: keys, options: &Config, }) for _, key := range keys { pm.consider(v.MapIndex(key)) } case reflect.Struct: numFields := v.NumField() for i := 0; i < numFields; i++ { pm.consider(v.Field(i)) } } } // addPointer to the pointerMap, update reusedPointers. Returns true if pointer was reused func (pm *pointerMap) addPointerReturnTrueIfWasReused(ptr uintptr) bool { // Is this allready known to be reused? for _, have := range pm.reusedPointers { if ptr == have { return true } } // Have we seen it once before? for _, seen := range pm.pointers { if ptr == seen { // Add it to the register of pointers we have seen more than once pm.reusedPointers = append(pm.reusedPointers, ptr) return true } } // This pointer was new to us pm.pointers = append(pm.pointers, ptr) return false } litter-1.2.0/print.go000066400000000000000000000016451352511105400145030ustar00rootroot00000000000000package litter import ( "io" "strconv" ) func printBool(w io.Writer, value bool) { if value { w.Write([]byte("true")) return } w.Write([]byte("false")) } func printInt(w io.Writer, val int64, base int) { w.Write([]byte(strconv.FormatInt(val, base))) } func printUint(w io.Writer, val uint64, base int) { w.Write([]byte(strconv.FormatUint(val, base))) } func printFloat(w io.Writer, val float64, precision int) { w.Write([]byte(strconv.FormatFloat(val, 'g', -1, precision))) } func printComplex(w io.Writer, c complex128, floatPrecision int) { w.Write([]byte("complex")) printInt(w, int64(floatPrecision*2), 10) r := real(c) w.Write([]byte("(")) w.Write([]byte(strconv.FormatFloat(r, 'g', -1, floatPrecision))) i := imag(c) if i >= 0 { w.Write([]byte("+")) } w.Write([]byte(strconv.FormatFloat(i, 'g', -1, floatPrecision))) w.Write([]byte("i)")) } func printNil(w io.Writer) { w.Write([]byte("nil")) } litter-1.2.0/testdata/000077500000000000000000000000001352511105400146235ustar00rootroot00000000000000litter-1.2.0/testdata/config_Compact.dump000066400000000000000000000003501352511105400204230ustar00rootroot00000000000000[]interface{}{litter_test.options{Compact:false,StripPackageNames:false,HidePrivateFields:true,HomePackage:"",Separator:" "},&litter_test.BasicStruct{Public:1,private:2},litter_test.Function,litter.Dump,func(string,int)(bool,error)}litter-1.2.0/testdata/config_FieldFilter.dump000066400000000000000000000002761352511105400212350ustar00rootroot00000000000000[]interface {}{ litter_test.options{ HomePackage: "", Separator: " ", }, &litter_test.BasicStruct{}, litter_test.Function, litter.Dump, func(string, int) (bool, error), }litter-1.2.0/testdata/config_HidePrivateFields.dump000066400000000000000000000004371352511105400223760ustar00rootroot00000000000000[]interface {}{ litter_test.options{ Compact: false, StripPackageNames: false, HidePrivateFields: true, HomePackage: "", Separator: " ", }, &litter_test.BasicStruct{ Public: 1, }, litter_test.Function, litter.Dump, func(string, int) (bool, error), }litter-1.2.0/testdata/config_HideZeroValues.dump000066400000000000000000000003501352511105400217260ustar00rootroot00000000000000[]interface {}{ litter_test.options{ HidePrivateFields: true, Separator: " ", }, &litter_test.BasicStruct{ Public: 1, private: 2, }, litter_test.Function, litter.Dump, func(string, int) (bool, error), }litter-1.2.0/testdata/config_HomePackage.dump000066400000000000000000000004131352511105400212010ustar00rootroot00000000000000[]interface {}{ options{ Compact: false, StripPackageNames: false, HidePrivateFields: true, HomePackage: "", Separator: " ", }, &BasicStruct{ Public: 1, private: 2, }, Function, litter.Dump, func(string, int) (bool, error), }litter-1.2.0/testdata/config_StripPackageNames.dump000066400000000000000000000004041352511105400223760ustar00rootroot00000000000000[]interface {}{ options{ Compact: false, StripPackageNames: false, HidePrivateFields: true, HomePackage: "", Separator: " ", }, &BasicStruct{ Public: 1, private: 2, }, Function, Dump, func(string, int) (bool, error), }litter-1.2.0/testdata/customDumper.dump000066400000000000000000000005451352511105400202050ustar00rootroot00000000000000map[string]interface {}{ "v1": *litter_test.CustomMultiLineDumper{ // p0 multi line }, "v2": p0, "v2x": *litter_test.CustomMultiLineDumper{ multi line }, "v3": litter_test.CustomSingleLineDumper, "v4": *litter_test.CustomSingleLineDumper, // p1 "v5": p1, "v6": *litter_test.CustomSingleLineDumper, }litter-1.2.0/testdata/maps.dump000066400000000000000000000004301352511105400164470ustar00rootroot00000000000000[]interface {}{ map[string]string{ "another string": "indeed", "hello": "there", "something": "something something", }, map[int]string{ 1: "one", 2: "two", 3: "three", }, map[int]*litter_test.BlankStruct{ 2: &litter_test.BlankStruct{}, }, }litter-1.2.0/testdata/multipleArgs_lineBreak.dump000066400000000000000000000000341352511105400221330ustar00rootroot00000000000000[]string{ "x", "y", } 42litter-1.2.0/testdata/multipleArgs_noSeparator.dump000066400000000000000000000000331352511105400225330ustar00rootroot00000000000000[]string{ "x", "y", }42litter-1.2.0/testdata/multipleArgs_separator.dump000066400000000000000000000000361352511105400222410ustar00rootroot00000000000000[]string{ "x", "y", }***42litter-1.2.0/testdata/nilIntefacesInStructs.dump000066400000000000000000000002311352511105400217710ustar00rootroot00000000000000[]*litter_test.InterfaceStruct{ &litter_test.InterfaceStruct{ // p0 Ifc: nil, }, &litter_test.InterfaceStruct{ Ifc: p0, }, p0, nil, }litter-1.2.0/testdata/pointerAliasing.dump000066400000000000000000000003121352511105400206360ustar00rootroot00000000000000[]*litter_test.RecursiveStruct{ &litter_test.RecursiveStruct{ // p0 Ptr: nil, }, p0, &litter_test.RecursiveStruct{ Ptr: p0, }, &litter_test.RecursiveStruct{ // p1 Ptr: p1, }, }litter-1.2.0/testdata/primitives.dump000066400000000000000000000006631352511105400177120ustar00rootroot00000000000000[]interface {}{ false, true, 7, 10, 10, 10, 10, 10, 10, 10, 10, 10, 12.3, 12.3, complex64(12+10.5i), complex128(-1.2-0.1i), "string with \"quote\"", []int{ 1, 2, 3, }, "hello from interface", litter_test.BlankStruct{}, &litter_test.BlankStruct{}, litter_test.BasicStruct{ Public: 1, private: 2, }, litter_test.Function, func(string) (bool, error), nil, nil, }litter-1.2.0/util.go000066400000000000000000000014011352511105400143120ustar00rootroot00000000000000package litter import ( "reflect" ) // deInterface 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 deInterface(v reflect.Value) reflect.Value { if v.Kind() == reflect.Interface && !v.IsNil() { v = v.Elem() } return v } func isPointerValue(v reflect.Value) bool { switch v.Kind() { case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Slice, reflect.UnsafePointer: return true } return false } func isZeroValue(v reflect.Value) bool { return (isPointerValue(v) && v.IsNil()) || (v.IsValid() && v.CanInterface() && reflect.DeepEqual(v.Interface(), reflect.Zero(v.Type()).Interface())) }