From 977780736f3b87e64567f388412f4e32f9a920ad Mon Sep 17 00:00:00 2001 From: John Guo Date: Thu, 21 Oct 2021 22:07:43 +0800 Subject: [PATCH] improve gutil.Dump --- encoding/gjson/gjson_api.go | 2 +- frame/g/g_func.go | 11 +- os/gview/gview_buildin.go | 2 +- util/gutil/gutil.go | 4 + util/gutil/gutil_dump.go | 263 +++++++++++++++++++++++++------- util/gutil/gutil_z_unit_test.go | 108 +++++++++++++ 6 files changed, 324 insertions(+), 66 deletions(-) diff --git a/encoding/gjson/gjson_api.go b/encoding/gjson/gjson_api.go index 6d0a3a067..b08a13947 100644 --- a/encoding/gjson/gjson_api.go +++ b/encoding/gjson/gjson_api.go @@ -184,5 +184,5 @@ func (j *Json) Dump() { func (j *Json) Export() string { j.mu.RLock() defer j.mu.RUnlock() - return gutil.Export(*j.p) + return gutil.Export(*j.p, gutil.ExportOption{}) } diff --git a/frame/g/g_func.go b/frame/g/g_func.go index d656c9b6d..b7185c6c0 100644 --- a/frame/g/g_func.go +++ b/frame/g/g_func.go @@ -34,13 +34,14 @@ func Listen() { } // Dump dumps a variable to stdout with more manually readable. -func Dump(i ...interface{}) { - gutil.Dump(i...) +func Dump(values ...interface{}) { + gutil.Dump(values...) } -// Export exports a variable to string with more manually readable. -func Export(i ...interface{}) string { - return gutil.Export(i...) +// DumpBrief acts like Dump, but with no type information. +// Also see Dump. +func DumpBrief(values ...interface{}) { + gutil.DumpBrief(values...) } // Throw throws an exception, which can be caught by TryCatch function. diff --git a/os/gview/gview_buildin.go b/os/gview/gview_buildin.go index 5e49d7b53..3da297f9a 100644 --- a/os/gview/gview_buildin.go +++ b/os/gview/gview_buildin.go @@ -26,7 +26,7 @@ import ( func (view *View) buildInFuncDump(values ...interface{}) (result string) { result += "\n" return result diff --git a/util/gutil/gutil.go b/util/gutil/gutil.go index d10e91195..b7a3ed32e 100644 --- a/util/gutil/gutil.go +++ b/util/gutil/gutil.go @@ -16,6 +16,10 @@ import ( "reflect" ) +const ( + dumpIndent = ` ` +) + // Throw throws out an exception, which can be caught be TryCatch or recover. func Throw(exception interface{}) { panic(exception) diff --git a/util/gutil/gutil_dump.go b/util/gutil/gutil_dump.go index a3b643ee8..c747ff570 100644 --- a/util/gutil/gutil_dump.go +++ b/util/gutil/gutil_dump.go @@ -9,12 +9,16 @@ package gutil import ( "bytes" "fmt" - "github.com/gogf/gf/v2/internal/json" - "github.com/gogf/gf/v2/util/gconv" - "os" + "github.com/gogf/gf/v2/internal/structs" + "github.com/gogf/gf/v2/text/gstr" "reflect" ) +// ExportOption specifies the behavior of function Export. +type ExportOption struct { + WithoutType bool // WithoutType specifies exported content has no type information. +} + // iVal is used for type assert api for Val(). type iVal interface { Val() interface{} @@ -30,66 +34,207 @@ type iMapStrAny interface { MapStrAny() map[string]interface{} } -// Dump prints variables to stdout with more manually readable. -func Dump(i ...interface{}) { - s := Export(i...) - if s != "" { - fmt.Println(s) +// Dump prints variables `values` to stdout with more manually readable. +func Dump(values ...interface{}) { + for _, value := range values { + if s := Export(value, ExportOption{ + WithoutType: false, + }); s != "" { + fmt.Println(s) + } } } -// Export returns variables as a string with more manually readable. -func Export(i ...interface{}) string { - buffer := bytes.NewBuffer(nil) - for _, value := range i { - switch r := value.(type) { - case []byte: - buffer.Write(r) - case string: - buffer.WriteString(r) - default: - var ( - reflectValue = reflect.ValueOf(value) - reflectKind = reflectValue.Kind() - ) - for reflectKind == reflect.Ptr { - reflectValue = reflectValue.Elem() - reflectKind = reflectValue.Kind() - } - switch reflectKind { - case reflect.Slice, reflect.Array: - value = gconv.Interfaces(value) - case reflect.Map: - value = gconv.Map(value) - case reflect.Struct: - converted := false - if r, ok := value.(iVal); ok { - if result := r.Val(); result != nil { - value = result - converted = true - } - } - if !converted { - if r, ok := value.(iMapStrAny); ok { - if result := r.MapStrAny(); result != nil { - value = result - converted = true - } - } - } - if !converted { - if r, ok := value.(iString); ok { - value = r.String() - } - } - } - encoder := json.NewEncoder(buffer) - encoder.SetEscapeHTML(false) - encoder.SetIndent("", "\t") - if err := encoder.Encode(value); err != nil { - fmt.Fprintln(os.Stderr, err.Error()) - } +// DumpBrief acts like Dump, but with no type information. +// Also see Dump. +func DumpBrief(values ...interface{}) { + for _, value := range values { + if s := Export(value, ExportOption{ + WithoutType: true, + }); s != "" { + fmt.Println(s) } } +} + +// Export returns variables `values` as a string with more manually readable. +func Export(value interface{}, option ExportOption) string { + buffer := bytes.NewBuffer(nil) + doExport(value, "", buffer, doExportOption{ + WithoutType: option.WithoutType, + }) return buffer.String() } + +type doExportOption struct { + WithoutType bool +} + +func doExport(value interface{}, indent string, buffer *bytes.Buffer, option doExportOption) { + var ( + reflectValue = reflect.ValueOf(value) + reflectKind = reflectValue.Kind() + reflectTypeName = reflectValue.Type().String() + newIndent = indent + dumpIndent + ) + if option.WithoutType { + reflectTypeName = "" + } + for reflectKind == reflect.Ptr { + reflectValue = reflectValue.Elem() + reflectKind = reflectValue.Kind() + } + switch reflectKind { + case reflect.Slice, reflect.Array: + if _, ok := value.([]byte); ok { + if option.WithoutType { + buffer.WriteString(fmt.Sprintf("\"%v\"\n", value)) + } else { + buffer.WriteString(fmt.Sprintf( + "%s(%d) \"%v\"\n", + reflectTypeName, + len(reflectValue.String()), + value, + )) + } + return + } + if reflectValue.Len() == 0 { + if option.WithoutType { + buffer.WriteString("[]") + } else { + buffer.WriteString(fmt.Sprintf("%s(0) []", reflectTypeName)) + } + return + } + if option.WithoutType { + buffer.WriteString("[\n") + } else { + buffer.WriteString(fmt.Sprintf("%s(%d) [\n", reflectTypeName, reflectValue.Len())) + } + for i := 0; i < reflectValue.Len(); i++ { + buffer.WriteString(newIndent) + doExport(reflectValue.Index(i).Interface(), newIndent, buffer, option) + buffer.WriteString(",\n") + } + buffer.WriteString(fmt.Sprintf("%s]", indent)) + + case reflect.Map: + var ( + mapKeys = reflectValue.MapKeys() + ) + if len(mapKeys) == 0 { + if option.WithoutType { + buffer.WriteString("{}") + } else { + buffer.WriteString(fmt.Sprintf("%s(0) {}", reflectTypeName)) + } + return + } + + var ( + maxSpaceNum = 0 + tmpSpaceNum = 0 + mapKeyStr = "" + ) + for _, key := range mapKeys { + tmpSpaceNum = len(fmt.Sprintf(`%v`, key.Interface())) + if tmpSpaceNum > maxSpaceNum { + maxSpaceNum = tmpSpaceNum + } + } + if option.WithoutType { + buffer.WriteString("{\n") + } else { + buffer.WriteString(fmt.Sprintf("%s(%d) {\n", reflectTypeName, len(mapKeys))) + } + for _, mapKey := range mapKeys { + tmpSpaceNum = len(fmt.Sprintf(`%v`, mapKey.Interface())) + if mapKey.Kind() == reflect.String { + mapKeyStr = fmt.Sprintf(`"%v"`, mapKey.Interface()) + } else { + mapKeyStr = fmt.Sprintf(`%v`, mapKey.Interface()) + } + if option.WithoutType { + buffer.WriteString(fmt.Sprintf( + "%s%v:%s", + newIndent, + mapKeyStr, + gstr.Repeat(" ", maxSpaceNum-tmpSpaceNum+1), + )) + } else { + buffer.WriteString(fmt.Sprintf( + "%s%s(%v):%s", + newIndent, + mapKey.Type().String(), + mapKeyStr, + gstr.Repeat(" ", maxSpaceNum-tmpSpaceNum+1), + )) + } + doExport(reflectValue.MapIndex(mapKey).Interface(), newIndent, buffer, option) + buffer.WriteString(",\n") + } + buffer.WriteString(fmt.Sprintf("%s}", indent)) + + case reflect.Struct: + structFields, _ := structs.Fields(structs.FieldsInput{ + Pointer: value, + RecursiveOption: structs.RecursiveOptionEmbeddedNoTag, + }) + if len(structFields) == 0 { + if option.WithoutType { + buffer.WriteString("{}") + } else { + buffer.WriteString(fmt.Sprintf("%s(0) {}", reflectTypeName)) + } + return + } + + var ( + maxSpaceNum = 0 + tmpSpaceNum = 0 + ) + for _, field := range structFields { + tmpSpaceNum = len(field.Name()) + if tmpSpaceNum > maxSpaceNum { + maxSpaceNum = tmpSpaceNum + } + } + if option.WithoutType { + buffer.WriteString("{\n") + } else { + buffer.WriteString(fmt.Sprintf("%s(%d) {\n", reflectTypeName, len(structFields))) + } + for _, field := range structFields { + tmpSpaceNum = len(fmt.Sprintf(`%v`, field.Name())) + buffer.WriteString(fmt.Sprintf( + "%s%s:%s", + newIndent, + field.Name(), + gstr.Repeat(" ", maxSpaceNum-tmpSpaceNum+1), + )) + doExport(field.Value.Interface(), newIndent, buffer, option) + buffer.WriteString(",\n") + } + buffer.WriteString(fmt.Sprintf("%s}", indent)) + + case reflect.String: + if option.WithoutType { + buffer.WriteString(fmt.Sprintf("\"%v\"", value)) + } else { + buffer.WriteString(fmt.Sprintf( + "%s(%d) \"%v\"", + reflectTypeName, + len(reflectValue.String()), + value, + )) + } + + default: + if option.WithoutType { + buffer.WriteString(fmt.Sprintf("%v", value)) + } else { + buffer.WriteString(fmt.Sprintf("%s(%v)", reflectTypeName, value)) + } + } +} diff --git a/util/gutil/gutil_z_unit_test.go b/util/gutil/gutil_z_unit_test.go index 09a8d244a..88b223c83 100755 --- a/util/gutil/gutil_z_unit_test.go +++ b/util/gutil/gutil_z_unit_test.go @@ -8,6 +8,8 @@ package gutil_test import ( "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/util/gmeta" "testing" "github.com/gogf/gf/v2/test/gtest" @@ -15,10 +17,116 @@ import ( ) func Test_Dump(t *testing.T) { + type CommonReq struct { + AppId int64 `json:"appId" v:"required" in:"path" des:"应用Id" sum:"应用Id Summary"` + ResourceId string `json:"resourceId" in:"query" des:"资源Id" sum:"资源Id Summary"` + } + type SetSpecInfo struct { + StorageType string `v:"required|in:CLOUD_PREMIUM,CLOUD_SSD,CLOUD_HSSD" des:"StorageType"` + Shards int32 `des:"shards 分片数" sum:"Shards Summary"` + Params []string `des:"默认参数(json 串-ClickHouseParams)" sum:"Params Summary"` + } + type CreateResourceReq struct { + CommonReq + gmeta.Meta `path:"/CreateResourceReq" method:"POST" tags:"default" sum:"CreateResourceReq sum"` + Name string `des:"实例名称"` + Product string `des:"业务类型"` + Region string `v:"required" des:"区域"` + SetMap map[string]*SetSpecInfo `v:"required" des:"配置Map"` + SetSlice []SetSpecInfo `v:"required" des:"配置Slice"` + Handler ghttp.HandlerFunc + internal string + } + req := &CreateResourceReq{ + CommonReq: CommonReq{ + AppId: 12345678, + ResourceId: "tdchqy-xxx", + }, + Name: "john", + Product: "goframe", + Region: "cd", + SetMap: map[string]*SetSpecInfo{ + "test1": { + StorageType: "ssd", + Shards: 2, + Params: []string{"a", "b", "c"}, + }, + "test2": { + StorageType: "hssd", + Shards: 10, + Params: []string{}, + }, + }, + SetSlice: []SetSpecInfo{ + { + StorageType: "hssd", + Shards: 10, + Params: []string{"h"}, + }, + }, + } gtest.C(t, func(t *gtest.T) { gutil.Dump(map[int]int{ 100: 100, }) + gutil.Dump(req) + }) +} + +func Test_DumpBrief(t *testing.T) { + type CommonReq struct { + AppId int64 `json:"appId" v:"required" in:"path" des:"应用Id" sum:"应用Id Summary"` + ResourceId string `json:"resourceId" in:"query" des:"资源Id" sum:"资源Id Summary"` + } + type SetSpecInfo struct { + StorageType string `v:"required|in:CLOUD_PREMIUM,CLOUD_SSD,CLOUD_HSSD" des:"StorageType"` + Shards int32 `des:"shards 分片数" sum:"Shards Summary"` + Params []string `des:"默认参数(json 串-ClickHouseParams)" sum:"Params Summary"` + } + type CreateResourceReq struct { + CommonReq + gmeta.Meta `path:"/CreateResourceReq" method:"POST" tags:"default" sum:"CreateResourceReq sum"` + Name string `des:"实例名称"` + Product string `des:"业务类型"` + Region string `v:"required" des:"区域"` + SetMap map[string]*SetSpecInfo `v:"required" des:"配置Map"` + SetSlice []SetSpecInfo `v:"required" des:"配置Slice"` + Handler ghttp.HandlerFunc + internal string + } + req := &CreateResourceReq{ + CommonReq: CommonReq{ + AppId: 12345678, + ResourceId: "tdchqy-xxx", + }, + Name: "john", + Product: "goframe", + Region: "cd", + SetMap: map[string]*SetSpecInfo{ + "test1": { + StorageType: "ssd", + Shards: 2, + Params: []string{"a", "b", "c"}, + }, + "test2": { + StorageType: "hssd", + Shards: 10, + Params: []string{}, + }, + }, + SetSlice: []SetSpecInfo{ + { + StorageType: "hssd", + Shards: 10, + Params: []string{"h"}, + }, + }, + } + gtest.C(t, func(t *gtest.T) { + gutil.DumpBrief(map[int]int{ + 100: 100, + }) + gutil.DumpBrief(req) }) }