package dump
import (
"fmt"
"io"
"os"
"path/filepath"
"reflect"
"runtime"
"strconv"
"strings"
"sync"
"time"
"github.com/gookit/goutil/strutil"
"github.com/gookit/goutil/x/ccolor"
)
type visit struct {
v uintptr
typ reflect .Type
}
type Dumper struct {
*Options
mu sync .RWMutex
visited map [visit ]int
msValue bool
curDepth int
indentBytes []byte
}
func NewDumper (out io .Writer , skip int ) *Dumper {
return &Dumper {
Options : NewDefaultOptions (out , skip ),
visited : make (map [visit ]int ),
}
}
func NewWithOptions (fns ...OptionFunc ) *Dumper {
return NewDumper (os .Stdout , defaultSkip ).WithOptions (fns ...)
}
func (d *Dumper ) WithSkip (skip int ) *Dumper {
d .CallerSkip = skip
return d
}
func (d *Dumper ) WithoutColor () *Dumper {
d .NoColor = true
return d
}
func (d *Dumper ) WithOptions (fns ...OptionFunc ) *Dumper {
for _ , fn := range fns {
fn (d .Options )
}
return d
}
func (d *Dumper ) ResetOptions () {
d .curDepth = 0
d .visited = make (map [visit ]int )
d .Options = NewDefaultOptions (os .Stdout , d .CallerSkip )
}
func (d *Dumper ) Dump (vs ...any ) { d .dump (vs ...) }
func (d *Dumper ) Print (vs ...any ) { d .dump (vs ...) }
func (d *Dumper ) Println (vs ...any ) { d .dump (vs ...) }
func (d *Dumper ) Fprint (w io .Writer , vs ...any ) {
backup := d .Output
d .Output = w
d .dump (vs ...)
d .Output = backup
}
func (d *Dumper ) dump (vs ...any ) {
d .curDepth = 0
d .visited = make (map [visit ]int )
if d .NoColor {
d .ColorTheme = make (Theme )
}
if d .ShowFlag != Fnopos {
pc , file , line , ok := runtime .Caller (d .CallerSkip )
if ok {
d .printCaller (pc , file , line )
}
}
for _ , v := range vs {
d .printOne (v )
}
}
func (d *Dumper ) printCaller (pc uintptr , file string , line int ) {
fnName := runtime .FuncForPC (pc ).Name ()
lineS := strconv .Itoa (line )
nodes := []string {"PRINT AT " }
for _ , flag := range callerFlags {
if d .ShowFlag &flag == 0 {
continue
}
switch flag {
case Ffunc :
nodes = append (nodes , fnName , "(" )
case Ffile :
nodes = append (nodes , file )
case Ffname :
fName := filepath .Base (file )
nodes = append (nodes , fName )
default :
nodes = append (nodes , ":" , lineS )
}
}
if len (nodes ) == 1 {
nodes = append (nodes , file , ":" , lineS )
} else if d .ShowFlag &Ffunc != 0 {
nodes = append (nodes , ")" )
}
text := strings .Join (nodes , "" )
d .print (d .ColorTheme .caller (text ), "\n" )
}
func (d *Dumper ) advance (step int ) {
d .curDepth += step
if d .curDepth < 1 {
d .indentBytes = []byte {}
return
}
d .indentBytes = strutil .RepeatBytes (d .IndentChar , d .IndentLen *d .curDepth )
}
func (d *Dumper ) printOne (v any ) {
if v == nil {
d .indentPrint ("<nil>,\n" )
return
}
if bts , ok := v .([]byte ); ok && d .BytesAsString {
strVal := d .ColorTheme .string (string (bts ))
if d .ShowLen {
lenTip := d .ColorTheme .valTip ("#len=" + strconv .Itoa (len (bts )) + ",cap=" + strconv .Itoa (cap (bts )))
d .printf ("[]byte(\"%s\"), %s\n" , strVal , lenTip )
} else {
d .printf ("[]byte(\"%s\"),\n" , strVal )
}
return
}
rv := reflect .ValueOf (v )
d .printRValue (rv .Type (), rv )
}
func (d *Dumper ) printRValue (t reflect .Type , v reflect .Value ) {
isPtr := t .Kind () == reflect .Ptr
if isPtr {
if v .IsNil () {
d .printf ("%s<nil>,\n" , t .String ())
return
}
v , t = v .Elem (), t .Elem ()
d .indentPrint ("&" )
}
if !v .IsValid () {
d .indentPrint (t .String (), "<nil>, #invalid\n" )
}
if d .curDepth > d .MaxDepth {
d .printf ("%s(!OVER MAX DEPTH!),\n" , v .String ())
return
}
switch t .Kind () {
case reflect .Bool :
d .printf ("%s(%v),\n" , t .String (), v .Bool ())
case reflect .Float32 , reflect .Float64 :
d .printf ("%s(%v),\n" , t .String (), v .Float ())
case reflect .Int , reflect .Int8 , reflect .Int16 , reflect .Int32 , reflect .Int64 :
intStr := strconv .FormatInt (v .Int (), 10 )
intStr = d .ColorTheme .integer (intStr )
d .printf ("%s(%s),%s\n" , t .String (), intStr , d .rvStringer (t , v ))
case reflect .Uint , reflect .Uint8 , reflect .Uint16 , reflect .Uint32 , reflect .Uint64 , reflect .Uintptr :
intStr := strconv .FormatUint (v .Uint (), 10 )
intStr = d .ColorTheme .integer (intStr )
d .printf ("%s(%s),%s\n" , t .String (), intStr , d .rvStringer (t , v ))
case reflect .String :
strVal := d .ColorTheme .string (v .String ())
if d .ShowLen {
lenTip := d .ColorTheme .valTip ("#len=" + strconv .Itoa (v .Len ()))
d .printf ("%s(\"%s\"), %s\n" , t .String (), strVal , lenTip )
} else {
d .printf ("%s(\"%s\"),\n" , t .String (), strVal )
}
case reflect .Complex64 , reflect .Complex128 :
d .printf ("%#v\n" , v .Complex ())
case reflect .Slice , reflect .Array :
if v .CanAddr () && !d .checkCyclicRef (t , v ) {
break
}
eleNum := v .Len ()
if d .ShowLen {
lenTip := d .ColorTheme .valTip ("#len=" + strconv .Itoa (eleNum ) + ",cap=" + strconv .Itoa (v .Cap ()))
d .write (!isPtr , t .String (), " [ " , lenTip , "\n" )
} else {
d .write (!isPtr , t .String (), " [\n" )
}
d .msValue = false
for i := 0 ; i < eleNum ; i ++ {
if i > d .MaxElementsNum {
d .indentPrint ("...(!OVER MAX SETTING!)...\n" )
break
}
sv := v .Index (i )
d .advance (1 )
d .printRValue (sv .Type (), sv )
d .advance (-1 )
}
d .indentPrint ("],\n" )
case reflect .Struct :
if v .CanAddr () && !d .checkCyclicRef (t , v ) {
break
}
if t == timeType {
d .printf ("time.Time(%s),\n" , d .ColorTheme .string (d .fmtTimeValue (v )))
break
}
if !isPtr && t .ConvertibleTo (timeType ) {
tv := v .Convert (timeType )
d .printf ("%s(%s),\n" , t .String (), d .ColorTheme .string (d .fmtTimeValue (tv )))
break
}
d .write (!isPtr , d .ColorTheme .msType (t .String ()), " {\n" )
d .msValue = false
fldNum := v .NumField ()
for i := 0 ; i < fldNum ; i ++ {
fName := t .Field (i ).Name
if d .SkipPrivate && isUnexported (fName ) {
continue
}
fv := v .Field (i )
if d .SkipNilField && isNilOrInvalid (fv ) {
continue
}
d .advance (1 )
d .indentPrint (d .ColorTheme .field (fName ), ": " )
d .msValue = true
d .printRValue (fv .Type (), fv )
d .msValue = false
d .advance (-1 )
}
d .indentPrint ("},\n" )
case reflect .Map :
if d .ShowLen {
lenTip := d .ColorTheme .valTip ("#len=" + strconv .Itoa (v .Len ()))
d .write (!isPtr , d .ColorTheme .msType (t .String ()), " { " , lenTip , "\n" )
} else {
d .write (!isPtr , d .ColorTheme .msType (t .String ()), " {\n" )
}
d .msValue = false
for _ , key := range v .MapKeys () {
mv := v .MapIndex (key )
if d .SkipNilField && isNilOrInvalid (mv ) {
continue
}
d .advance (1 )
if !key .CanInterface () {
d .printf ("%s: " , key .String ())
} else {
d .printf ("%#v: " , key .Interface ())
}
if mv .CanAddr () && !d .checkCyclicRef (mv .Type (), mv ) {
d .advance (-1 )
continue
}
d .msValue = true
d .printRValue (mv .Type (), mv )
d .msValue = false
d .advance (-1 )
}
d .indentPrint ("},\n" )
case reflect .Interface :
if v .CanAddr () && !d .checkCyclicRef (t , v ) {
break
}
switch e := v .Elem (); {
case e .Kind () == reflect .Invalid :
d .indentPrint ("nil,\n" )
case e .IsValid ():
d .printRValue (e .Type (), e )
default :
d .indentPrint (t .String (), "(nil),\n" )
}
case reflect .Chan :
d .printf ("(%s)(%#v),\n" , t .String (), v .Pointer ())
case reflect .Func :
d .printf ("(%s) {...},\n" , t .String ())
case reflect .UnsafePointer :
d .printf ("(%#v),\n" , v .Pointer ())
case reflect .Invalid :
d .indentPrint (t .String (), "(nil),\n" )
default :
if v .CanAddr () && !d .checkCyclicRef (t , v ) {
break
}
if v .CanInterface () {
d .printf ("%s(%#v),\n" , t .String (), v .Interface ())
} else {
d .printf ("%s(%v),\n" , t .String (), v .String ())
}
}
}
func (d *Dumper ) checkCyclicRef (t reflect .Type , v reflect .Value ) (goon bool ) {
addr := v .UnsafeAddr ()
vis := visit {addr , t }
d .mu .RLock ()
if vd , ok := d .visited [vis ]; ok && vd < d .MaxDepth {
d .indentPrint (t .String (), "{(!CYCLIC REFERENCE!)}\n" )
d .mu .RUnlock ()
return false
}
d .mu .RUnlock ()
d .mu .Lock ()
d .visited [vis ] = d .curDepth
d .mu .Unlock ()
return true
}
func (d *Dumper ) rvStringer (rt reflect .Type , rv reflect .Value ) string {
if rv .CanInterface () && rt .Implements (stringerType ) {
return d .ColorTheme .valTip (` #str: "` + rv .Interface ().(fmt .Stringer ).String () + `"` )
}
return ""
}
func (d *Dumper ) fmtTimeValue (v reflect .Value ) string {
var timeStr string
if v .CanInterface () {
timeStr = v .Interface ().(time .Time ).Format (time .RFC3339 )
} else {
timeStr = v .String ()
}
return timeStr
}
func (d *Dumper ) print (v ...any ) {
if d .NoColor {
_, _ = fmt .Fprint (d .Output , v ...)
} else {
ccolor .Fprint (d .Output , v ...)
}
}
func (d *Dumper ) printf (f string , v ...any ) {
if !d .msValue {
_, _ = d .Output .Write (d .indentBytes )
}
if d .NoColor {
_, _ = fmt .Fprintf (d .Output , f , v ...)
} else {
ccolor .Fprintf (d .Output , f , v ...)
}
}
func (d *Dumper ) write (indent bool , v ...any ) {
if indent && !d .msValue {
_, _ = d .Output .Write (d .indentBytes )
}
if d .NoColor {
_, _ = fmt .Fprint (d .Output , v ...)
} else {
ccolor .Fprint (d .Output , v ...)
}
}
func (d *Dumper ) indentPrint (v ...any ) {
d .write (true , v ...)
}
The pages are generated with Golds v0.8.4 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .