github.com/bingoohuang/gg@v0.0.0-20240325092523-45da7dee9335/pkg/dump/dumper.go (about)

     1  package dump
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"os"
     7  	"path"
     8  	"reflect"
     9  	"runtime"
    10  	"strconv"
    11  	"strings"
    12  	"sync"
    13  
    14  	"github.com/gookit/color"
    15  	"github.com/gookit/goutil/strutil"
    16  )
    17  
    18  // Options for dump vars
    19  type Options struct {
    20  	// Output the output writer
    21  	Output io.Writer
    22  	// NoType dont show data type TODO
    23  	NoType bool
    24  	// NoColor don't with color
    25  	NoColor bool
    26  	// IndentLen width. default is 2
    27  	IndentLen int
    28  	// IndentChar default is one space
    29  	IndentChar byte
    30  	// MaxDepth for nested print
    31  	MaxDepth int
    32  	// ShowFlag for display caller position
    33  	ShowFlag int
    34  	// MoreLenNL array/slice elements length > MoreLenNL, will wrap new line
    35  	// MoreLenNL int
    36  	// CallerSkip skip for call runtime.Caller()
    37  	CallerSkip int
    38  	// ColorTheme for print result.
    39  	ColorTheme Theme
    40  }
    41  
    42  // printValue must keep track of already-printed pointer values to avoid
    43  // infinite recursion. refer the pkg: github.com/kr/pretty
    44  type visit struct {
    45  	v   uintptr
    46  	typ reflect.Type
    47  }
    48  
    49  // Dumper struct definition
    50  type Dumper struct {
    51  	*Options
    52  	// visited struct records
    53  	visited map[visit]int
    54  	// is value in the slice, map, struct. will not apply indent.
    55  	msValue bool
    56  	// current depth
    57  	curDepth int
    58  	// current indent string bytes
    59  	indentBytes []byte
    60  	// prevDepth, nextDepth int
    61  	// indentStr, indentPrev, lineEnd string
    62  
    63  	lock sync.Mutex
    64  }
    65  
    66  // NewDumper create
    67  func NewDumper(out io.Writer, skip int) *Dumper {
    68  	return &Dumper{
    69  		Options: NewDefaultOptions(out, skip),
    70  		// init map
    71  		visited: make(map[visit]int),
    72  	}
    73  }
    74  
    75  // NewWithOptions create
    76  func NewWithOptions(fn func(opts *Options)) *Dumper {
    77  	d := NewDumper(os.Stdout, 3)
    78  	fn(d.Options)
    79  
    80  	return d
    81  }
    82  
    83  // NewDefaultOptions create.
    84  func NewDefaultOptions(out io.Writer, skip int) *Options {
    85  	if out == nil {
    86  		out = os.Stdout
    87  	}
    88  
    89  	return &Options{
    90  		Output: out,
    91  		// ---
    92  		MaxDepth: 5,
    93  		ShowFlag: Ffunc | Ffname | Fline,
    94  		// MoreLenNL: 8,
    95  		// ---
    96  		IndentLen:  2,
    97  		IndentChar: ' ',
    98  		CallerSkip: skip,
    99  		ColorTheme: defaultTheme,
   100  	}
   101  }
   102  
   103  // WithSkip for dumper
   104  func (d *Dumper) WithSkip(skip int) *Dumper {
   105  	d.CallerSkip = skip
   106  	return d
   107  }
   108  
   109  // WithoutColor for dumper
   110  func (d *Dumper) WithoutColor() *Dumper {
   111  	d.NoColor = true
   112  	return d
   113  }
   114  
   115  // WithOptions for dumper
   116  func (d *Dumper) WithOptions(fn func(opts *Options)) *Dumper {
   117  	fn(d.Options)
   118  	return d
   119  }
   120  
   121  // ResetOptions for dumper
   122  func (d *Dumper) ResetOptions() {
   123  	d.curDepth = 0
   124  	d.visited = make(map[visit]int)
   125  	d.Options = NewDefaultOptions(os.Stdout, 2)
   126  }
   127  
   128  // Dump vars
   129  func (d *Dumper) Dump(vs ...interface{}) {
   130  	d.dump(vs...)
   131  }
   132  
   133  // Print vars. alias of Dump()
   134  func (d *Dumper) Print(vs ...interface{}) {
   135  	d.dump(vs...)
   136  }
   137  
   138  // Println vars. alias of Dump()
   139  func (d *Dumper) Println(vs ...interface{}) {
   140  	d.dump(vs...)
   141  }
   142  
   143  // Fprint print vars to io.Writer
   144  func (d *Dumper) Fprint(w io.Writer, vs ...interface{}) {
   145  	out := d.Output // backup
   146  
   147  	d.Output = w
   148  	d.dump(vs...)
   149  	d.Output = out // restore
   150  }
   151  
   152  // dump go vars
   153  func (d *Dumper) dump(vs ...interface{}) {
   154  	d.lock.Lock()
   155  	defer d.lock.Unlock()
   156  
   157  	// reset some settings.
   158  	d.curDepth = 0
   159  	d.visited = make(map[visit]int)
   160  	if d.NoColor { // clear all theme settings.
   161  		d.ColorTheme = make(Theme)
   162  	}
   163  
   164  	// show print position
   165  	if d.ShowFlag != Fnopos {
   166  		// get the print position
   167  		pc, file, line, ok := runtime.Caller(d.CallerSkip)
   168  		if ok {
   169  			d.printCaller(pc, file, line)
   170  		}
   171  	}
   172  
   173  	// print var data
   174  	for _, v := range vs {
   175  		// d.advance(1)
   176  		d.printOne(v)
   177  		// d.advance(-1)
   178  	}
   179  }
   180  
   181  func (d *Dumper) printCaller(pc uintptr, file string, line int) {
   182  	// eg: github.com/gookit/goutil/dump.ExamplePrint
   183  	fnName := runtime.FuncForPC(pc).Name()
   184  
   185  	lineS := strconv.Itoa(line)
   186  	nodes := []string{"PRINT AT "}
   187  
   188  	// eg:
   189  	// "PRINT AT github.com/gookit/goutil/dump.ExamplePrint(goutil/dump/dump_test.go:23)"
   190  	// "PRINT AT github.com/gookit/goutil/dump.ExamplePrint(dump_test.go:23)"
   191  	// "PRINT AT github.com/gookit/goutil/dump.ExamplePrint(:23)"
   192  	for _, flag := range callerFlags {
   193  		// has flag
   194  		if d.ShowFlag&flag == 0 {
   195  			continue
   196  		}
   197  		switch flag {
   198  		case Ffunc: // full func name
   199  			nodes = append(nodes, fnName, "(")
   200  		case Ffile: // full file path
   201  			nodes = append(nodes, file)
   202  		case Ffname: // only file name
   203  			fName := path.Base(file) // file name
   204  			nodes = append(nodes, fName)
   205  		case Fline:
   206  			nodes = append(nodes, ":", lineS)
   207  		}
   208  	}
   209  
   210  	// fallback. eg: "PRINT AT goutil/dump/dump_test.go:23"
   211  	if len(nodes) == 1 {
   212  		nodes = append(nodes, file, ":", lineS)
   213  	} else if d.ShowFlag&Ffunc != 0 { // has func, add ")"
   214  		nodes = append(nodes, ")")
   215  	}
   216  
   217  	text := strings.Join(nodes, "")
   218  
   219  	d.print(d.ColorTheme.caller(text), "\n")
   220  }
   221  
   222  func (d *Dumper) advance(step int) {
   223  	d.curDepth += step
   224  	// d.nextDepth = d.curDepth + step
   225  	d.indentBytes = strutil.RepeatBytes(d.IndentChar, d.IndentLen*d.curDepth)
   226  }
   227  
   228  func (d *Dumper) printOne(v interface{}) {
   229  	if v == nil {
   230  		d.indentPrint("<nil>,\n")
   231  		return
   232  	}
   233  
   234  	rv := reflect.ValueOf(v)
   235  	d.printRValue(rv.Type(), rv)
   236  }
   237  
   238  func (d *Dumper) printRValue(t reflect.Type, v reflect.Value) {
   239  	// var isPtr bool
   240  	// if is an ptr, get real type and value
   241  	if t.Kind() == reflect.Ptr {
   242  		if v.IsNil() {
   243  			d.printf("%s<nil>,\n", t.String())
   244  			return
   245  		}
   246  
   247  		v = v.Elem()
   248  		t = t.Elem()
   249  		// add "*" prefix
   250  		d.indentPrint("&")
   251  	}
   252  
   253  	if !v.IsValid() {
   254  		d.indentPrint(t.String(), "<nil>, #invalid\n")
   255  	}
   256  
   257  	if d.curDepth > d.MaxDepth {
   258  		if !v.CanInterface() {
   259  			d.printf("%s,\n", v.String())
   260  		} else {
   261  			d.printf("%#v,\n", v.Interface())
   262  		}
   263  		return
   264  	}
   265  
   266  	switch t.Kind() {
   267  	case reflect.Bool:
   268  		d.printf("%s(%v),\n", t.String(), v.Bool())
   269  	case reflect.Float32, reflect.Float64:
   270  		d.printf("%s(%v),\n", t.String(), v.Float())
   271  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
   272  		intStr := strconv.FormatInt(v.Int(), 10)
   273  		intStr = d.ColorTheme.integer(intStr)
   274  		d.printf("%s(%s),\n", t.String(), intStr)
   275  	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
   276  		intStr := strconv.FormatUint(v.Uint(), 10)
   277  		intStr = d.ColorTheme.integer(intStr)
   278  		d.printf("%s(%s),\n", t.String(), intStr)
   279  	case reflect.String:
   280  		strVal := d.ColorTheme.string(v.String())
   281  		lenTip := d.ColorTheme.lenTip("#len=" + strconv.Itoa(v.Len()))
   282  		d.printf("%s(\"%s\"), %s\n", t.String(), strVal, lenTip)
   283  	case reflect.Complex64, reflect.Complex128:
   284  		d.printf("%#v\n", v.Complex())
   285  	case reflect.Slice, reflect.Array:
   286  		eleNum := v.Len()
   287  		lenTip := d.ColorTheme.lenTip("#len=" + strconv.Itoa(eleNum))
   288  
   289  		d.indentPrint(t.String(), " [ ", lenTip, "\n")
   290  		d.msValue = false
   291  		for i := 0; i < eleNum; i++ {
   292  			sv := v.Index(i)
   293  			d.advance(1)
   294  
   295  			// d.msValue = true
   296  			d.printRValue(sv.Type(), sv)
   297  			// d.msValue = false
   298  
   299  			// d.printf("%v,\n", v.Index(i).Interface())
   300  			d.advance(-1)
   301  		}
   302  
   303  		d.indentPrint("],\n")
   304  	case reflect.Struct:
   305  		if v.CanAddr() {
   306  			addr := v.UnsafeAddr()
   307  			vis := visit{addr, t}
   308  			if vd, ok := d.visited[vis]; ok && vd < d.MaxDepth {
   309  				d.indentPrint(t.String(), "{(!CYCLIC REFERENCE!)}\n")
   310  				break // don't print v again
   311  			}
   312  			d.visited[vis] = d.curDepth
   313  		}
   314  
   315  		d.indentPrint(d.ColorTheme.msType(t.String()), " {\n")
   316  		d.msValue = false
   317  
   318  		fldNum := v.NumField()
   319  		for i := 0; i < fldNum; i++ {
   320  			fv := v.Field(i)
   321  			d.advance(1)
   322  
   323  			fName := t.Field(i).Name
   324  			d.indentPrint(d.ColorTheme.field(fName), ": ")
   325  
   326  			d.msValue = true
   327  			d.printRValue(fv.Type(), fv)
   328  			d.msValue = false
   329  
   330  			d.advance(-1)
   331  		}
   332  
   333  		d.indentPrint("},\n")
   334  	case reflect.Map:
   335  		lenTip := d.ColorTheme.lenTip("#len=" + strconv.Itoa(v.Len()))
   336  		d.indentPrint(d.ColorTheme.msType(t.String()), " { ", lenTip, "\n")
   337  		d.msValue = false
   338  
   339  		for _, key := range v.MapKeys() {
   340  			mv := v.MapIndex(key)
   341  			d.advance(1)
   342  
   343  			// print key name
   344  			if !key.CanInterface() {
   345  				// d.printf("<cyan>%s</>: ", key.String())
   346  				d.printf("%s: ", key.String())
   347  			} else {
   348  				d.printf("%#v: ", key.Interface())
   349  			}
   350  
   351  			// print field value
   352  			d.msValue = true
   353  			d.printRValue(mv.Type(), mv)
   354  			d.msValue = false
   355  
   356  			d.advance(-1)
   357  		}
   358  
   359  		d.indentPrint("},\n")
   360  	case reflect.Interface:
   361  		switch e := v.Elem(); {
   362  		case e.Kind() == reflect.Invalid:
   363  			d.indentPrint("nil,\n")
   364  		case e.IsValid():
   365  			// d.advance(1)
   366  			d.printRValue(e.Type(), e)
   367  		default:
   368  			d.indentPrint(t.String(), "(nil),\n")
   369  		}
   370  	// case reflect.Ptr:
   371  	case reflect.Chan:
   372  		d.printf("(%s)(%#v),\n", t.String(), v.Pointer())
   373  	case reflect.Func:
   374  		d.printf("(%s) {...},\n", t.String())
   375  	case reflect.UnsafePointer:
   376  		d.printf("(%#v),\n", v.Pointer())
   377  	case reflect.Invalid:
   378  		d.indentPrint(t.String(), "(nil),\n")
   379  	default:
   380  		if v.CanInterface() {
   381  			d.printf("%s(%#v),\n", t.String(), v.Interface())
   382  		} else {
   383  			d.printf("%s(%v),\n", t.String(), v.String())
   384  		}
   385  	}
   386  }
   387  
   388  func (d *Dumper) print(v ...interface{}) {
   389  	if d.NoColor {
   390  		_, _ = fmt.Fprint(d.Output, v...)
   391  	} else {
   392  		color.Fprint(d.Output, v...)
   393  	}
   394  }
   395  
   396  func (d *Dumper) printf(f string, v ...interface{}) {
   397  	if !d.msValue {
   398  		_, _ = d.Output.Write(d.indentBytes)
   399  	}
   400  
   401  	if d.NoColor {
   402  		_, _ = fmt.Fprintf(d.Output, f, v...)
   403  	} else {
   404  		color.Fprintf(d.Output, f, v...)
   405  	}
   406  }
   407  
   408  func (d *Dumper) indentPrint(v ...interface{}) {
   409  	if !d.msValue {
   410  		_, _ = d.Output.Write(d.indentBytes)
   411  	}
   412  
   413  	if d.NoColor {
   414  		_, _ = fmt.Fprint(d.Output, v...)
   415  	} else {
   416  		color.Fprint(d.Output, v...)
   417  	}
   418  }