github.com/wangyougui/gf/v2@v2.6.5/util/gutil/gutil_dump.go (about)

     1  // Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
     2  //
     3  // This Source Code Form is subject to the terms of the MIT License.
     4  // If a copy of the MIT was not distributed with this file,
     5  // You can obtain one at https://github.com/wangyougui/gf.
     6  
     7  package gutil
     8  
     9  import (
    10  	"bytes"
    11  	"encoding/json"
    12  	"fmt"
    13  	"io"
    14  	"reflect"
    15  	"strings"
    16  
    17  	"github.com/wangyougui/gf/v2/internal/reflection"
    18  	"github.com/wangyougui/gf/v2/os/gstructs"
    19  	"github.com/wangyougui/gf/v2/text/gstr"
    20  )
    21  
    22  // iString is used for type assert api for String().
    23  type iString interface {
    24  	String() string
    25  }
    26  
    27  // iError is used for type assert api for Error().
    28  type iError interface {
    29  	Error() string
    30  }
    31  
    32  // iMarshalJSON is the interface for custom Json marshaling.
    33  type iMarshalJSON interface {
    34  	MarshalJSON() ([]byte, error)
    35  }
    36  
    37  // DumpOption specifies the behavior of function Export.
    38  type DumpOption struct {
    39  	WithType     bool // WithType specifies dumping content with type information.
    40  	ExportedOnly bool // Only dump Exported fields for structs.
    41  }
    42  
    43  // Dump prints variables `values` to stdout with more manually readable.
    44  func Dump(values ...interface{}) {
    45  	for _, value := range values {
    46  		DumpWithOption(value, DumpOption{
    47  			WithType:     false,
    48  			ExportedOnly: false,
    49  		})
    50  	}
    51  }
    52  
    53  // DumpWithType acts like Dump, but with type information.
    54  // Also see Dump.
    55  func DumpWithType(values ...interface{}) {
    56  	for _, value := range values {
    57  		DumpWithOption(value, DumpOption{
    58  			WithType:     true,
    59  			ExportedOnly: false,
    60  		})
    61  	}
    62  }
    63  
    64  // DumpWithOption returns variables `values` as a string with more manually readable.
    65  func DumpWithOption(value interface{}, option DumpOption) {
    66  	buffer := bytes.NewBuffer(nil)
    67  	DumpTo(buffer, value, DumpOption{
    68  		WithType:     option.WithType,
    69  		ExportedOnly: option.ExportedOnly,
    70  	})
    71  	fmt.Println(buffer.String())
    72  }
    73  
    74  // DumpTo writes variables `values` as a string in to `writer` with more manually readable
    75  func DumpTo(writer io.Writer, value interface{}, option DumpOption) {
    76  	buffer := bytes.NewBuffer(nil)
    77  	doDump(value, "", buffer, doDumpOption{
    78  		WithType:     option.WithType,
    79  		ExportedOnly: option.ExportedOnly,
    80  	})
    81  	_, _ = writer.Write(buffer.Bytes())
    82  }
    83  
    84  type doDumpOption struct {
    85  	WithType         bool
    86  	ExportedOnly     bool
    87  	DumpedPointerSet map[string]struct{}
    88  }
    89  
    90  func doDump(value interface{}, indent string, buffer *bytes.Buffer, option doDumpOption) {
    91  	if option.DumpedPointerSet == nil {
    92  		option.DumpedPointerSet = map[string]struct{}{}
    93  	}
    94  
    95  	if value == nil {
    96  		buffer.WriteString(`<nil>`)
    97  		return
    98  	}
    99  	var reflectValue reflect.Value
   100  	if v, ok := value.(reflect.Value); ok {
   101  		reflectValue = v
   102  		if v.IsValid() && v.CanInterface() {
   103  			value = v.Interface()
   104  		} else {
   105  			if convertedValue, ok := reflection.ValueToInterface(v); ok {
   106  				value = convertedValue
   107  			}
   108  		}
   109  	} else {
   110  		reflectValue = reflect.ValueOf(value)
   111  	}
   112  	var reflectKind = reflectValue.Kind()
   113  	// Double check nil value.
   114  	if value == nil || reflectKind == reflect.Invalid {
   115  		buffer.WriteString(`<nil>`)
   116  		return
   117  	}
   118  	var (
   119  		reflectTypeName = reflectValue.Type().String()
   120  		ptrAddress      string
   121  		newIndent       = indent + dumpIndent
   122  	)
   123  	reflectTypeName = strings.ReplaceAll(reflectTypeName, `[]uint8`, `[]byte`)
   124  	for reflectKind == reflect.Ptr {
   125  		if ptrAddress == "" {
   126  			ptrAddress = fmt.Sprintf(`0x%x`, reflectValue.Pointer())
   127  		}
   128  		reflectValue = reflectValue.Elem()
   129  		reflectKind = reflectValue.Kind()
   130  	}
   131  	var (
   132  		exportInternalInput = doDumpInternalInput{
   133  			Value:            value,
   134  			Indent:           indent,
   135  			NewIndent:        newIndent,
   136  			Buffer:           buffer,
   137  			Option:           option,
   138  			PtrAddress:       ptrAddress,
   139  			ReflectValue:     reflectValue,
   140  			ReflectTypeName:  reflectTypeName,
   141  			ExportedOnly:     option.ExportedOnly,
   142  			DumpedPointerSet: option.DumpedPointerSet,
   143  		}
   144  	)
   145  	switch reflectKind {
   146  	case reflect.Slice, reflect.Array:
   147  		doDumpSlice(exportInternalInput)
   148  
   149  	case reflect.Map:
   150  		doDumpMap(exportInternalInput)
   151  
   152  	case reflect.Struct:
   153  		doDumpStruct(exportInternalInput)
   154  
   155  	case reflect.String:
   156  		doDumpString(exportInternalInput)
   157  
   158  	case reflect.Bool:
   159  		doDumpBool(exportInternalInput)
   160  
   161  	case
   162  		reflect.Int,
   163  		reflect.Int8,
   164  		reflect.Int16,
   165  		reflect.Int32,
   166  		reflect.Int64,
   167  		reflect.Uint,
   168  		reflect.Uint8,
   169  		reflect.Uint16,
   170  		reflect.Uint32,
   171  		reflect.Uint64,
   172  		reflect.Float32,
   173  		reflect.Float64,
   174  		reflect.Complex64,
   175  		reflect.Complex128:
   176  		doDumpNumber(exportInternalInput)
   177  
   178  	case reflect.Chan:
   179  		buffer.WriteString(fmt.Sprintf(`<%s>`, reflectValue.Type().String()))
   180  
   181  	case reflect.Func:
   182  		if reflectValue.IsNil() || !reflectValue.IsValid() {
   183  			buffer.WriteString(`<nil>`)
   184  		} else {
   185  			buffer.WriteString(fmt.Sprintf(`<%s>`, reflectValue.Type().String()))
   186  		}
   187  
   188  	case reflect.Interface:
   189  		doDump(exportInternalInput.ReflectValue.Elem(), indent, buffer, option)
   190  
   191  	default:
   192  		doDumpDefault(exportInternalInput)
   193  	}
   194  }
   195  
   196  type doDumpInternalInput struct {
   197  	Value            interface{}
   198  	Indent           string
   199  	NewIndent        string
   200  	Buffer           *bytes.Buffer
   201  	Option           doDumpOption
   202  	ReflectValue     reflect.Value
   203  	ReflectTypeName  string
   204  	PtrAddress       string
   205  	ExportedOnly     bool
   206  	DumpedPointerSet map[string]struct{}
   207  }
   208  
   209  func doDumpSlice(in doDumpInternalInput) {
   210  	if b, ok := in.Value.([]byte); ok {
   211  		if !in.Option.WithType {
   212  			in.Buffer.WriteString(fmt.Sprintf(`"%s"`, addSlashesForString(string(b))))
   213  		} else {
   214  			in.Buffer.WriteString(fmt.Sprintf(
   215  				`%s(%d) "%s"`,
   216  				in.ReflectTypeName,
   217  				len(string(b)),
   218  				string(b),
   219  			))
   220  		}
   221  		return
   222  	}
   223  	if in.ReflectValue.Len() == 0 {
   224  		if !in.Option.WithType {
   225  			in.Buffer.WriteString("[]")
   226  		} else {
   227  			in.Buffer.WriteString(fmt.Sprintf("%s(0) []", in.ReflectTypeName))
   228  		}
   229  		return
   230  	}
   231  	if !in.Option.WithType {
   232  		in.Buffer.WriteString("[\n")
   233  	} else {
   234  		in.Buffer.WriteString(fmt.Sprintf("%s(%d) [\n", in.ReflectTypeName, in.ReflectValue.Len()))
   235  	}
   236  	for i := 0; i < in.ReflectValue.Len(); i++ {
   237  		in.Buffer.WriteString(in.NewIndent)
   238  		doDump(in.ReflectValue.Index(i), in.NewIndent, in.Buffer, in.Option)
   239  		in.Buffer.WriteString(",\n")
   240  	}
   241  	in.Buffer.WriteString(fmt.Sprintf("%s]", in.Indent))
   242  }
   243  
   244  func doDumpMap(in doDumpInternalInput) {
   245  	var mapKeys = make([]reflect.Value, 0)
   246  	for _, key := range in.ReflectValue.MapKeys() {
   247  		if !key.CanInterface() {
   248  			continue
   249  		}
   250  		mapKey := key
   251  		mapKeys = append(mapKeys, mapKey)
   252  	}
   253  	if len(mapKeys) == 0 {
   254  		if !in.Option.WithType {
   255  			in.Buffer.WriteString("{}")
   256  		} else {
   257  			in.Buffer.WriteString(fmt.Sprintf("%s(0) {}", in.ReflectTypeName))
   258  		}
   259  		return
   260  	}
   261  	var (
   262  		maxSpaceNum = 0
   263  		tmpSpaceNum = 0
   264  		mapKeyStr   = ""
   265  	)
   266  	for _, key := range mapKeys {
   267  		tmpSpaceNum = len(fmt.Sprintf(`%v`, key.Interface()))
   268  		if tmpSpaceNum > maxSpaceNum {
   269  			maxSpaceNum = tmpSpaceNum
   270  		}
   271  	}
   272  	if !in.Option.WithType {
   273  		in.Buffer.WriteString("{\n")
   274  	} else {
   275  		in.Buffer.WriteString(fmt.Sprintf("%s(%d) {\n", in.ReflectTypeName, len(mapKeys)))
   276  	}
   277  	for _, mapKey := range mapKeys {
   278  		tmpSpaceNum = len(fmt.Sprintf(`%v`, mapKey.Interface()))
   279  		if mapKey.Kind() == reflect.String {
   280  			mapKeyStr = fmt.Sprintf(`"%v"`, mapKey.Interface())
   281  		} else {
   282  			mapKeyStr = fmt.Sprintf(`%v`, mapKey.Interface())
   283  		}
   284  		// Map key and indent string dump.
   285  		if !in.Option.WithType {
   286  			in.Buffer.WriteString(fmt.Sprintf(
   287  				"%s%v:%s",
   288  				in.NewIndent,
   289  				mapKeyStr,
   290  				strings.Repeat(" ", maxSpaceNum-tmpSpaceNum+1),
   291  			))
   292  		} else {
   293  			in.Buffer.WriteString(fmt.Sprintf(
   294  				"%s%s(%v):%s",
   295  				in.NewIndent,
   296  				mapKey.Type().String(),
   297  				mapKeyStr,
   298  				strings.Repeat(" ", maxSpaceNum-tmpSpaceNum+1),
   299  			))
   300  		}
   301  		// Map value dump.
   302  		doDump(in.ReflectValue.MapIndex(mapKey), in.NewIndent, in.Buffer, in.Option)
   303  		in.Buffer.WriteString(",\n")
   304  	}
   305  	in.Buffer.WriteString(fmt.Sprintf("%s}", in.Indent))
   306  }
   307  
   308  func doDumpStruct(in doDumpInternalInput) {
   309  	if in.PtrAddress != "" {
   310  		if _, ok := in.DumpedPointerSet[in.PtrAddress]; ok {
   311  			in.Buffer.WriteString(fmt.Sprintf(`<cycle dump %s>`, in.PtrAddress))
   312  			return
   313  		}
   314  	}
   315  	in.DumpedPointerSet[in.PtrAddress] = struct{}{}
   316  
   317  	structFields, _ := gstructs.Fields(gstructs.FieldsInput{
   318  		Pointer:         in.Value,
   319  		RecursiveOption: gstructs.RecursiveOptionEmbedded,
   320  	})
   321  	var (
   322  		hasNoExportedFields = true
   323  		_, isReflectValue   = in.Value.(reflect.Value)
   324  	)
   325  	for _, field := range structFields {
   326  		if field.IsExported() {
   327  			hasNoExportedFields = false
   328  			break
   329  		}
   330  	}
   331  	if !isReflectValue && (len(structFields) == 0 || hasNoExportedFields) {
   332  		var (
   333  			structContentStr  = ""
   334  			attributeCountStr = "0"
   335  		)
   336  		if v, ok := in.Value.(iString); ok {
   337  			structContentStr = v.String()
   338  		} else if v, ok := in.Value.(iError); ok {
   339  			structContentStr = v.Error()
   340  		} else if v, ok := in.Value.(iMarshalJSON); ok {
   341  			b, _ := v.MarshalJSON()
   342  			structContentStr = string(b)
   343  		} else {
   344  			// Has no common interface implements.
   345  			if len(structFields) != 0 {
   346  				goto dumpStructFields
   347  			}
   348  		}
   349  		if structContentStr == "" {
   350  			structContentStr = "{}"
   351  		} else {
   352  			structContentStr = fmt.Sprintf(`"%s"`, addSlashesForString(structContentStr))
   353  			attributeCountStr = fmt.Sprintf(`%d`, len(structContentStr)-2)
   354  		}
   355  		if !in.Option.WithType {
   356  			in.Buffer.WriteString(structContentStr)
   357  		} else {
   358  			in.Buffer.WriteString(fmt.Sprintf(
   359  				"%s(%s) %s",
   360  				in.ReflectTypeName,
   361  				attributeCountStr,
   362  				structContentStr,
   363  			))
   364  		}
   365  		return
   366  	}
   367  
   368  dumpStructFields:
   369  	var (
   370  		maxSpaceNum = 0
   371  		tmpSpaceNum = 0
   372  	)
   373  	for _, field := range structFields {
   374  		if in.ExportedOnly && !field.IsExported() {
   375  			continue
   376  		}
   377  		tmpSpaceNum = len(field.Name())
   378  		if tmpSpaceNum > maxSpaceNum {
   379  			maxSpaceNum = tmpSpaceNum
   380  		}
   381  	}
   382  	if !in.Option.WithType {
   383  		in.Buffer.WriteString("{\n")
   384  	} else {
   385  		in.Buffer.WriteString(fmt.Sprintf("%s(%d) {\n", in.ReflectTypeName, len(structFields)))
   386  	}
   387  	for _, field := range structFields {
   388  		if in.ExportedOnly && !field.IsExported() {
   389  			continue
   390  		}
   391  		tmpSpaceNum = len(fmt.Sprintf(`%v`, field.Name()))
   392  		in.Buffer.WriteString(fmt.Sprintf(
   393  			"%s%s:%s",
   394  			in.NewIndent,
   395  			field.Name(),
   396  			strings.Repeat(" ", maxSpaceNum-tmpSpaceNum+1),
   397  		))
   398  		doDump(field.Value, in.NewIndent, in.Buffer, in.Option)
   399  		in.Buffer.WriteString(",\n")
   400  	}
   401  	in.Buffer.WriteString(fmt.Sprintf("%s}", in.Indent))
   402  }
   403  
   404  func doDumpNumber(in doDumpInternalInput) {
   405  	if v, ok := in.Value.(iString); ok {
   406  		s := v.String()
   407  		if !in.Option.WithType {
   408  			in.Buffer.WriteString(fmt.Sprintf(`"%v"`, addSlashesForString(s)))
   409  		} else {
   410  			in.Buffer.WriteString(fmt.Sprintf(
   411  				`%s(%d) "%v"`,
   412  				in.ReflectTypeName,
   413  				len(s),
   414  				addSlashesForString(s),
   415  			))
   416  		}
   417  	} else {
   418  		doDumpDefault(in)
   419  	}
   420  }
   421  
   422  func doDumpString(in doDumpInternalInput) {
   423  	s := in.ReflectValue.String()
   424  	if !in.Option.WithType {
   425  		in.Buffer.WriteString(fmt.Sprintf(`"%v"`, addSlashesForString(s)))
   426  	} else {
   427  		in.Buffer.WriteString(fmt.Sprintf(
   428  			`%s(%d) "%v"`,
   429  			in.ReflectTypeName,
   430  			len(s),
   431  			addSlashesForString(s),
   432  		))
   433  	}
   434  }
   435  
   436  func doDumpBool(in doDumpInternalInput) {
   437  	var s string
   438  	if in.ReflectValue.Bool() {
   439  		s = `true`
   440  	} else {
   441  		s = `false`
   442  	}
   443  	if in.Option.WithType {
   444  		s = fmt.Sprintf(`bool(%s)`, s)
   445  	}
   446  	in.Buffer.WriteString(s)
   447  }
   448  
   449  func doDumpDefault(in doDumpInternalInput) {
   450  	var s string
   451  	if in.ReflectValue.IsValid() && in.ReflectValue.CanInterface() {
   452  		s = fmt.Sprintf("%v", in.ReflectValue.Interface())
   453  	}
   454  	if s == "" {
   455  		s = fmt.Sprintf("%v", in.Value)
   456  	}
   457  	s = gstr.Trim(s, `<>`)
   458  	if !in.Option.WithType {
   459  		in.Buffer.WriteString(s)
   460  	} else {
   461  		in.Buffer.WriteString(fmt.Sprintf("%s(%s)", in.ReflectTypeName, s))
   462  	}
   463  }
   464  
   465  func addSlashesForString(s string) string {
   466  	return gstr.ReplaceByMap(s, map[string]string{
   467  		`"`:  `\"`,
   468  		"\r": `\r`,
   469  		"\t": `\t`,
   470  		"\n": `\n`,
   471  	})
   472  }
   473  
   474  // DumpJson pretty dumps json content to stdout.
   475  func DumpJson(jsonContent string) {
   476  	var (
   477  		buffer    = bytes.NewBuffer(nil)
   478  		jsonBytes = []byte(jsonContent)
   479  	)
   480  	if err := json.Indent(buffer, jsonBytes, "", "\t"); err != nil {
   481  		fmt.Println(err.Error())
   482  	}
   483  	fmt.Println(buffer.String())
   484  }