Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<a href="https://goreportcard.com/report/github.com/goforj/godump"><img src="https://goreportcard.com/badge/github.com/goforj/godump" alt="Go Report Card"></a>
<a href="https://codecov.io/gh/goforj/godump" ><img src="https://codecov.io/gh/goforj/godump/graph/badge.svg?token=ULUTXL03XC"/></a>
<!-- test-count:embed:start -->
<img src="https://img.shields.io/badge/tests-143-brightgreen" alt="Tests">
<img src="https://img.shields.io/badge/tests-162-brightgreen" alt="Tests">
<!-- test-count:embed:end -->
<a href="https://github.com/avelino/awesome-go?tab=readme-ov-file#parsersencodersdecoders"><img src="https://awesome.re/mentioned-badge-flat.svg" alt="Mentioned in Awesome Go"></a>
</p>
Expand Down Expand Up @@ -583,9 +583,9 @@ Param n must be 0 or greater or this will be ignored, and default MaxDepth will
v := map[string]map[string]int{"a": {"b": 1}}
d := godump.NewDumper(godump.WithMaxDepth(1))
d.Dump(v)
// #map[string]int {
// #map[string]map[string]int {
// a => #map[string]int {
// b => ... (max depth)
// b => 1 #int
// }
// }
```
Expand Down
4 changes: 2 additions & 2 deletions examples/withmaxdepth/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ func main() {
v := map[string]map[string]int{"a": {"b": 1}}
d := godump.NewDumper(godump.WithMaxDepth(1))
d.Dump(v)
// #map[string]int {
// #map[string]map[string]int {
// a => #map[string]int {
// b => ... (max depth)
// b => 1 #int
// }
// }
}
95 changes: 77 additions & 18 deletions godump.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,9 +174,9 @@ func newDumpState() *dumpState {
// v := map[string]map[string]int{"a": {"b": 1}}
// d := godump.NewDumper(godump.WithMaxDepth(1))
// d.Dump(v)
// // #map[string]int {
// // #map[string]map[string]int {
// // a => #map[string]int {
// // b => ... (max depth)
// // b => 1 #int
// // }
// // }
func WithMaxDepth(n int) Option {
Expand Down Expand Up @@ -904,15 +904,22 @@ func (d *Dumper) getTypeString(t reflect.Type) string {
}

func (d *Dumper) printValue(w io.Writer, v reflect.Value, indent int, state *dumpState) {
if indent > d.maxDepth {
fmt.Fprint(w, d.colorize(colorGray, "... (max depth)"))
return
}
if !v.IsValid() {
fmt.Fprint(w, d.colorize(colorGray, "<invalid>"))
return
}

if isNil(v) {
typeStr := d.getTypeString(v.Type())
fmt.Fprintf(w, d.colorize(colorLime, typeStr)+d.colorize(colorGray, "(nil)"))
return
}

if shouldTruncateAtDepth(v, indent, d.maxDepth) {
fmt.Fprint(w, d.colorize(colorGray, "... (max depth)"))
return
}

if s := d.asStringer(v); s != "" {
fmt.Fprint(w, s)
return
Expand All @@ -921,18 +928,7 @@ func (d *Dumper) printValue(w io.Writer, v reflect.Value, indent int, state *dum
switch v.Kind() {
case reflect.Chan:
typ := d.colorizer(colorGray, d.getTypeString(v.Type()))

if v.IsNil() {
fmt.Fprint(w, d.colorize(colorGray, fmt.Sprintf("#%s(nil)", typ)))
} else {
fmt.Fprintf(w, "%s(%s)", d.colorize(colorGray, typ), d.colorize(colorCyan, fmt.Sprintf("%#x", v.Pointer())))
}
return
}

if isNil(v) {
typeStr := d.getTypeString(v.Type())
fmt.Fprintf(w, d.colorize(colorLime, typeStr)+d.colorize(colorGray, "(nil)"))
fmt.Fprintf(w, "%s(%s)", d.colorize(colorGray, typ), d.colorize(colorCyan, fmt.Sprintf("%#x", v.Pointer())))
return
}

Expand Down Expand Up @@ -1168,13 +1164,15 @@ func detectColor() bool {
return true
}

// newColorizer picks the appropriate colorizer based on environment overrides.
func newColorizer() Colorizer {
if detectColor() {
return colorizeANSI
}
return colorizeUnstyled
}

// contains reports whether target exists in the candidates slice.
func contains(candidates []reflect.Kind, target reflect.Kind) bool {
for _, candidate := range candidates {
if candidate == target {
Expand All @@ -1185,17 +1183,20 @@ func contains(candidates []reflect.Kind, target reflect.Kind) bool {
return false
}

// shouldIncludeField returns true when the field survives include/exclude filtering (include takes precedence).
func (d *Dumper) shouldIncludeField(name string) bool {
if len(d.includeFields) > 0 && !d.matchesAny(name, d.includeFields, FieldMatchExact) {
return false
}
return !d.matchesAny(name, d.excludeFields, d.fieldMatchMode)
}

// shouldRedactField reports whether the field should be replaced with the redacted placeholder.
func (d *Dumper) shouldRedactField(name string) bool {
return d.matchesAny(name, d.redactFields, d.redactMatchMode)
}

// matchesAny checks whether name matches any of the candidates using the provided mode.
func (d *Dumper) matchesAny(name string, candidates []string, mode FieldMatchMode) bool {
if len(candidates) == 0 {
return false
Expand Down Expand Up @@ -1235,3 +1236,61 @@ func (d *Dumper) redactedValue(v reflect.Value) string {
typeStr := d.getTypeString(v.Type())
return d.colorize(colorRed, "<redacted>") + d.colorize(colorGray, " #"+typeStr)
}

// isComplexValue reports whether v unwraps to a struct/map/slice/array.
func isComplexValue(v reflect.Value) bool {
_, ok := complexBaseKind(v)
return ok
}

// complexBaseKind unwraps interfaces/pointers, rejects nil, and returns the underlying complex kind if present.
func complexBaseKind(v reflect.Value) (reflect.Kind, bool) {
if !v.IsValid() {
return 0, false
}

for {
switch v.Kind() {
case reflect.Interface:
if v.IsNil() {
return 0, false
}
v = v.Elem()
case reflect.Ptr:
if v.IsNil() {
return 0, false
}
v = v.Elem()
default:
switch v.Kind() {
case reflect.Struct, reflect.Map, reflect.Slice, reflect.Array:
return v.Kind(), true
default:
return 0, false
}
}
}
Comment on lines +1252 to +1272
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason to do not do this ?

Suggested change
for {
switch v.Kind() {
case reflect.Interface:
if v.IsNil() {
return 0, false
}
v = v.Elem()
case reflect.Ptr:
if v.IsNil() {
return 0, false
}
v = v.Elem()
default:
switch v.Kind() {
case reflect.Struct, reflect.Map, reflect.Slice, reflect.Array:
return v.Kind(), true
default:
return 0, false
}
}
}
for {
switch v.Kind() {
case reflect.Interface:
if v.IsNil() {
return 0, false
}
v = v.Elem()
case reflect.Ptr:
if v.IsNil() {
return 0, false
}
v = v.Elem()
case reflect.Struct, reflect.Map, reflect.Slice, reflect.Array:
return v.Kind(), true
default:
return 0, false
}
}

}

// shouldTruncateAtDepth determines whether we should print a truncation placeholder at this depth for complex values.
func shouldTruncateAtDepth(v reflect.Value, indent, maxDepth int) bool {
if indent < maxDepth {
return false
}

kind, ok := complexBaseKind(v)
if indent > maxDepth {
return ok
}

if !ok {
return false
}

switch kind {
case reflect.Map, reflect.Slice, reflect.Array:
return true
default:
return false
}
}
Loading