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 }