From 8c1db293871429e48426c09e2a5edc458d8f2aba Mon Sep 17 00:00:00 2001 From: John Guo Date: Tue, 26 Mar 2024 20:25:33 +0800 Subject: [PATCH 1/6] fix: #3390 name&shor tag mapping failed to command input object for package gcmd --- os/gcmd/gcmd_command_object.go | 37 ++++++++++- os/gcmd/gcmd_z_unit_issue_test.go | 101 ++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+), 3 deletions(-) create mode 100644 os/gcmd/gcmd_z_unit_issue_test.go diff --git a/os/gcmd/gcmd_command_object.go b/os/gcmd/gcmd_command_object.go index 68c8610ad11..17ee1eecf00 100644 --- a/os/gcmd/gcmd_command_object.go +++ b/os/gcmd/gcmd_command_object.go @@ -251,6 +251,12 @@ func newCommandFromMethod( return } + // Options creating. + var paramKeyToAttrMap map[string]string + if paramKeyToAttrMap, err = newTagToAttrMappingFromInput(inputObject.Interface()); err != nil { + return + } + // ============================================================================================= // Create function that has value return. // ============================================================================================= @@ -278,8 +284,11 @@ func newCommandFromMethod( if arg.Orphan { if orphanValue := parser.GetOpt(arg.Name); orphanValue != nil { if orphanValue.String() == "" { - // Eg: gf -f + // Example: gf -f data[arg.Name] = "true" + if arg.Short != "" { + data[arg.Short] = "true" + } } else { // Adapter with common user habits. // Eg: @@ -301,9 +310,9 @@ func newCommandFromMethod( return fmt.Sprintf(`input command data map: %s`, gjson.MustEncode(data)) }) if inputObject.Kind() == reflect.Ptr { - err = gconv.Scan(data, inputObject.Interface()) + err = gconv.Scan(data, inputObject.Interface(), paramKeyToAttrMap) } else { - err = gconv.Struct(data, inputObject.Addr().Interface()) + err = gconv.Struct(data, inputObject.Addr().Interface(), paramKeyToAttrMap) } intlog.PrintFunc(ctx, func() string { return fmt.Sprintf(`input object assigned data: %s`, gjson.MustEncode(inputObject.Interface())) @@ -333,6 +342,28 @@ func newCommandFromMethod( return } +func newTagToAttrMappingFromInput(object interface{}) (paramKeyToAttrMap map[string]string, err error) { + var fields []gstructs.Field + fields, err = gstructs.Fields(gstructs.FieldsInput{ + Pointer: object, + RecursiveOption: gstructs.RecursiveOptionEmbeddedNoTag, + }) + paramKeyToAttrMap = make(map[string]string) + for _, field := range fields { + var ( + nameValue = field.Tag(tagNameName) + shortValue = field.Tag(tagNameShort) + ) + if nameValue != "" { + paramKeyToAttrMap[nameValue] = field.Name() + } + if shortValue != "" { + paramKeyToAttrMap[shortValue] = field.Name() + } + } + return +} + func newArgumentsFromInput(object interface{}) (args []Argument, err error) { var ( fields []gstructs.Field diff --git a/os/gcmd/gcmd_z_unit_issue_test.go b/os/gcmd/gcmd_z_unit_issue_test.go new file mode 100644 index 00000000000..c3f9814f479 --- /dev/null +++ b/os/gcmd/gcmd_z_unit_issue_test.go @@ -0,0 +1,101 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +// go test *.go -bench=".*" -benchmem + +package gcmd_test + +import ( + "context" + "testing" + + "github.com/gogf/gf/v2/encoding/gjson" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gcmd" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/test/gtest" +) + +type Issue3390CommandCase1 struct { + *gcmd.Command +} + +type Issue3390TestCase1 struct { + g.Meta `name:"index" ad:"test"` +} + +type Issue3390Case1Input struct { + g.Meta `name:"index"` + A string `short:"a" name:"aa"` + Be string `short:"b" name:"bb"` +} + +type Issue3390Case1Output struct { + Content string +} + +func (c Issue3390TestCase1) Index(ctx context.Context, in Issue3390Case1Input) (out *Issue3390Case1Output, err error) { + out = &Issue3390Case1Output{ + Content: gjson.MustEncodeString(in), + } + return +} + +func Test_Issue3390_Case1(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + root, err := gcmd.NewFromObject(Issue3390TestCase1{}) + if err != nil { + panic(err) + } + command := &Issue3390CommandCase1{root} + value, err := command.RunWithSpecificArgs( + gctx.New(), + []string{"main", "-a", "aaa", "-b", "bbb"}, + ) + t.AssertNil(err) + t.Assert(value.(*Issue3390Case1Output).Content, `{"A":"aaa","Be":"bbb"}`) + }) +} + +type Issue3390CommandCase2 struct { + *gcmd.Command +} + +type Issue3390TestCase2 struct { + g.Meta `name:"index" ad:"test"` +} + +type Issue3390Case2Input struct { + g.Meta `name:"index"` + A string `short:"b" name:"bb"` + Be string `short:"a" name:"aa"` +} + +type Issue3390Case2Output struct { + Content string +} + +func (c Issue3390TestCase2) Index(ctx context.Context, in Issue3390Case2Input) (out *Issue3390Case2Output, err error) { + out = &Issue3390Case2Output{ + Content: gjson.MustEncodeString(in), + } + return +} +func Test_Issue3390_Case2(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + root, err := gcmd.NewFromObject(Issue3390TestCase2{}) + if err != nil { + panic(err) + } + command := &Issue3390CommandCase2{root} + value, err := command.RunWithSpecificArgs( + gctx.New(), + []string{"main", "-a", "aaa", "-b", "bbb"}, + ) + t.AssertNil(err) + t.Assert(value.(*Issue3390Case2Output).Content, `{"A":"bbb","Be":"aaa"}`) + }) +} From 8e59e88cb81e00d3ae2dcb4f6694c6506e004c4b Mon Sep 17 00:00:00 2001 From: John Guo Date: Wed, 27 Mar 2024 09:34:29 +0800 Subject: [PATCH 2/6] fix #3417 --- internal/command/command.go | 10 +- os/gcmd/gcmd.go | 6 +- os/gcmd/gcmd_command_object.go | 42 +++----- os/gcmd/gcmd_command_run.go | 22 ++-- os/gcmd/gcmd_parser.go | 21 +++- os/gcmd/gcmd_z_unit_issue_test.go | 161 ++++++++++++++++++++++++++++-- util/gconv/gconv_struct.go | 10 ++ 7 files changed, 211 insertions(+), 61 deletions(-) diff --git a/internal/command/command.go b/internal/command/command.go index 688201eb133..7cac390279b 100644 --- a/internal/command/command.go +++ b/internal/command/command.go @@ -17,7 +17,7 @@ import ( var ( defaultParsedArgs = make([]string, 0) defaultParsedOptions = make(map[string]string) - argumentRegex = regexp.MustCompile(`^\-{1,2}([\w\?\.\-]+)(=){0,1}(.*)$`) + argumentOptionRegex = regexp.MustCompile(`^\-{1,2}([\w\?\.\-]+)(=){0,1}(.*)$`) ) // Init does custom initialization. @@ -41,22 +41,22 @@ func ParseUsingDefaultAlgorithm(args ...string) (parsedArgs []string, parsedOpti parsedArgs = make([]string, 0) parsedOptions = make(map[string]string) for i := 0; i < len(args); { - array := argumentRegex.FindStringSubmatch(args[i]) + array := argumentOptionRegex.FindStringSubmatch(args[i]) if len(array) > 2 { if array[2] == "=" { parsedOptions[array[1]] = array[3] } else if i < len(args)-1 { if len(args[i+1]) > 0 && args[i+1][0] == '-' { - // Eg: gf gen -d -n 1 + // Example: gf gen -d -n 1 parsedOptions[array[1]] = array[3] } else { - // Eg: gf gen -n 2 + // Example: gf gen -n 2 parsedOptions[array[1]] = args[i+1] i += 2 continue } } else { - // Eg: gf gen -h + // Example: gf gen -h parsedOptions[array[1]] = array[3] } } else { diff --git a/os/gcmd/gcmd.go b/os/gcmd/gcmd.go index 41b7eff03a2..b73644e913e 100644 --- a/os/gcmd/gcmd.go +++ b/os/gcmd/gcmd.go @@ -18,9 +18,9 @@ import ( ) const ( - CtxKeyParser gctx.StrKey = `CtxKeyParser` - CtxKeyCommand gctx.StrKey = `CtxKeyCommand` - CtxKeyArguments gctx.StrKey = `CtxKeyArguments` + CtxKeyParser gctx.StrKey = `CtxKeyParser` + CtxKeyCommand gctx.StrKey = `CtxKeyCommand` + CtxKeyArgumentsIndex gctx.StrKey = `CtxKeyArgumentsIndex` ) const ( diff --git a/os/gcmd/gcmd_command_object.go b/os/gcmd/gcmd_command_object.go index 17ee1eecf00..43ecc4f9103 100644 --- a/os/gcmd/gcmd_command_object.go +++ b/os/gcmd/gcmd_command_object.go @@ -251,11 +251,8 @@ func newCommandFromMethod( return } - // Options creating. - var paramKeyToAttrMap map[string]string - if paramKeyToAttrMap, err = newTagToAttrMappingFromInput(inputObject.Interface()); err != nil { - return - } + // For input struct converting using priority tag. + var priorityTag = gstr.Join([]string{tagNameName, tagNameShort}, ",") // ============================================================================================= // Create function that has value return. @@ -265,9 +262,16 @@ func newCommandFromMethod( var ( data = gconv.Map(parser.GetOptAll()) argIndex = 0 - arguments = gconv.Strings(ctx.Value(CtxKeyArguments)) + arguments = parser.GetArgAll() inputValues = []reflect.Value{reflect.ValueOf(ctx)} ) + if value := ctx.Value(CtxKeyArgumentsIndex); value != nil { + argIndex = value.(int) + // Use the left args to assign to input struct object. + if argIndex < len(arguments) { + arguments = arguments[argIndex:] + } + } if data == nil { data = map[string]interface{}{} } @@ -310,9 +314,9 @@ func newCommandFromMethod( return fmt.Sprintf(`input command data map: %s`, gjson.MustEncode(data)) }) if inputObject.Kind() == reflect.Ptr { - err = gconv.Scan(data, inputObject.Interface(), paramKeyToAttrMap) + err = gconv.StructTag(data, inputObject.Interface(), priorityTag) } else { - err = gconv.Struct(data, inputObject.Addr().Interface(), paramKeyToAttrMap) + err = gconv.StructTag(data, inputObject.Addr().Interface(), priorityTag) } intlog.PrintFunc(ctx, func() string { return fmt.Sprintf(`input object assigned data: %s`, gjson.MustEncode(inputObject.Interface())) @@ -342,28 +346,6 @@ func newCommandFromMethod( return } -func newTagToAttrMappingFromInput(object interface{}) (paramKeyToAttrMap map[string]string, err error) { - var fields []gstructs.Field - fields, err = gstructs.Fields(gstructs.FieldsInput{ - Pointer: object, - RecursiveOption: gstructs.RecursiveOptionEmbeddedNoTag, - }) - paramKeyToAttrMap = make(map[string]string) - for _, field := range fields { - var ( - nameValue = field.Tag(tagNameName) - shortValue = field.Tag(tagNameShort) - ) - if nameValue != "" { - paramKeyToAttrMap[nameValue] = field.Name() - } - if shortValue != "" { - paramKeyToAttrMap[shortValue] = field.Name() - } - } - return -} - func newArgumentsFromInput(object interface{}) (args []Argument, err error) { var ( fields []gstructs.Field diff --git a/os/gcmd/gcmd_command_run.go b/os/gcmd/gcmd_command_run.go index 499da83dfee..80e29842ded 100644 --- a/os/gcmd/gcmd_command_run.go +++ b/os/gcmd/gcmd_command_run.go @@ -93,7 +93,7 @@ func (c *Command) RunWithSpecificArgs(ctx context.Context, args []string) (value parsedArgs = parsedArgs[1:] // Find the matched command and run it. - lastCmd, foundCmd, newCtx := c.searchCommand(ctx, parsedArgs) + lastCmd, foundCmd, newCtx := c.searchCommand(ctx, parsedArgs, 0) if foundCmd != nil { return foundCmd.doRun(newCtx, args, parser) } @@ -215,25 +215,27 @@ func (c *Command) reParse(ctx context.Context, args []string, parser *Parser) (* } // searchCommand recursively searches the command according given arguments. -func (c *Command) searchCommand(ctx context.Context, args []string) (lastCmd, foundCmd *Command, newCtx context.Context) { +func (c *Command) searchCommand( + ctx context.Context, args []string, fromArgIndex int, +) (lastCmd, foundCmd *Command, newCtx context.Context) { if len(args) == 0 { return c, nil, ctx } for _, cmd := range c.commands { // Recursively searching the command. + // String comparison case-sensitive. if cmd.Name == args[0] { leftArgs := args[1:] // If this command needs argument, - // it then gives all its left arguments to it. - if cmd.hasArgumentFromIndex() { - ctx = context.WithValue(ctx, CtxKeyArguments, leftArgs) + // it then gives all its left arguments to it using arg index marks. + // + // Note that the args here (using default args parsing) could be different with the args + // that are parsed in command. + if cmd.hasArgumentFromIndex() || len(leftArgs) == 0 { + ctx = context.WithValue(ctx, CtxKeyArgumentsIndex, fromArgIndex+1) return c, cmd, ctx } - // Recursively searching. - if len(leftArgs) == 0 { - return c, cmd, ctx - } - return cmd.searchCommand(ctx, leftArgs) + return cmd.searchCommand(ctx, leftArgs, fromArgIndex+1) } } return c, nil, ctx diff --git a/os/gcmd/gcmd_parser.go b/os/gcmd/gcmd_parser.go index 49cbcca8502..2dcf9479e05 100644 --- a/os/gcmd/gcmd_parser.go +++ b/os/gcmd/gcmd_parser.go @@ -185,11 +185,24 @@ func (p *Parser) isOptionNeedArgument(name string) bool { // setOptionValue sets the option value for name and according alias. func (p *Parser) setOptionValue(name, value string) { + // Accurate option name match. for optionName := range p.passedOptions { - array := gstr.SplitAndTrim(optionName, ",") - for _, v := range array { - if strings.EqualFold(v, name) { - for _, v := range array { + optionNameAndShort := gstr.SplitAndTrim(optionName, ",") + for _, optionNameItem := range optionNameAndShort { + if optionNameItem == name { + for _, v := range optionNameAndShort { + p.parsedOptions[v] = value + } + return + } + } + } + // Fuzzy option name match. + for optionName := range p.passedOptions { + optionNameAndShort := gstr.SplitAndTrim(optionName, ",") + for _, optionNameItem := range optionNameAndShort { + if strings.EqualFold(optionNameItem, name) { + for _, v := range optionNameAndShort { p.parsedOptions[v] = value } return diff --git a/os/gcmd/gcmd_z_unit_issue_test.go b/os/gcmd/gcmd_z_unit_issue_test.go index c3f9814f479..88ba6714aee 100644 --- a/os/gcmd/gcmd_z_unit_issue_test.go +++ b/os/gcmd/gcmd_z_unit_issue_test.go @@ -45,12 +45,12 @@ func (c Issue3390TestCase1) Index(ctx context.Context, in Issue3390Case1Input) ( } func Test_Issue3390_Case1(t *testing.T) { + root, err := gcmd.NewFromObject(Issue3390TestCase1{}) + if err != nil { + panic(err) + } + command := &Issue3390CommandCase1{root} gtest.C(t, func(t *gtest.T) { - root, err := gcmd.NewFromObject(Issue3390TestCase1{}) - if err != nil { - panic(err) - } - command := &Issue3390CommandCase1{root} value, err := command.RunWithSpecificArgs( gctx.New(), []string{"main", "-a", "aaa", "-b", "bbb"}, @@ -85,17 +85,160 @@ func (c Issue3390TestCase2) Index(ctx context.Context, in Issue3390Case2Input) ( return } func Test_Issue3390_Case2(t *testing.T) { + root, err := gcmd.NewFromObject(Issue3390TestCase2{}) + if err != nil { + panic(err) + } + command := &Issue3390CommandCase2{root} + gtest.C(t, func(t *gtest.T) { + value, err := command.RunWithSpecificArgs( + gctx.New(), + []string{"main", "-a", "aaa", "-b", "bbb"}, + ) + t.AssertNil(err) + t.Assert(value.(*Issue3390Case2Output).Content, `{"A":"bbb","Be":"aaa"}`) + }) +} + +type Issue3390CommandCase3 struct { + *gcmd.Command +} + +type Issue3390TestCase3 struct { + g.Meta `name:"index" ad:"test"` +} + +type Issue3390Case3Input struct { + g.Meta `name:"index"` + A string `short:"b"` + Be string `short:"a"` +} + +type Issue3390Case3Output struct { + Content string +} + +func (c Issue3390TestCase3) Index(ctx context.Context, in Issue3390Case3Input) (out *Issue3390Case3Output, err error) { + out = &Issue3390Case3Output{ + Content: gjson.MustEncodeString(in), + } + return +} +func Test_Issue3390_Case3(t *testing.T) { + root, err := gcmd.NewFromObject(Issue3390TestCase3{}) + if err != nil { + panic(err) + } + command := &Issue3390CommandCase3{root} gtest.C(t, func(t *gtest.T) { - root, err := gcmd.NewFromObject(Issue3390TestCase2{}) + value, err := command.RunWithSpecificArgs( + gctx.New(), + []string{"main", "-a", "aaa", "-b", "bbb"}, + ) + t.AssertNil(err) + t.Assert(value.(*Issue3390Case3Output).Content, `{"A":"bbb","Be":"aaa"}`) + }) +} + +type Issue3390CommandCase4 struct { + *gcmd.Command +} + +type Issue3390TestCase4 struct { + g.Meta `name:"index" ad:"test"` +} + +type Issue3390Case4Input struct { + g.Meta `name:"index"` + A string `short:"a"` + Be string `short:"b"` +} + +type Issue3390Case4Output struct { + Content string +} + +func (c Issue3390TestCase4) Index(ctx context.Context, in Issue3390Case4Input) (out *Issue3390Case4Output, err error) { + out = &Issue3390Case4Output{ + Content: gjson.MustEncodeString(in), + } + return +} + +func Test_Issue3390_Case4(t *testing.T) { + root, err := gcmd.NewFromObject(Issue3390TestCase4{}) + if err != nil { + panic(err) + } + command := &Issue3390CommandCase4{root} + gtest.C(t, func(t *gtest.T) { + value, err := command.RunWithSpecificArgs( + gctx.New(), + []string{"main", "-a", "aaa", "-b", "bbb"}, + ) + t.AssertNil(err) + t.Assert(value.(*Issue3390Case4Output).Content, `{"A":"aaa","Be":"bbb"}`) + }) +} + +type Issue3417Test struct { + g.Meta `name:"root"` +} + +type Issue3417BuildInput struct { + g.Meta `name:"build" config:"gfcli.build"` + File string `name:"FILE" arg:"true" brief:"building file path"` + Name string `short:"n" name:"name" brief:"output binary name"` + Version string `short:"v" name:"version" brief:"output binary version"` + Arch string `short:"a" name:"arch" brief:"output binary architecture, multiple arch separated with ','"` + System string `short:"s" name:"system" brief:"output binary system, multiple os separated with ','"` + Output string `short:"o" name:"output" brief:"output binary path, used when building single binary file"` + Path string `short:"p" name:"path" brief:"output binary directory path, default is '.'" d:"."` + Extra string `short:"e" name:"extra" brief:"extra custom \"go build\" options"` + Mod string `short:"m" name:"mod" brief:"like \"-mod\" option of \"go build\", use \"-m none\" to disable go module"` + Cgo bool `short:"c" name:"cgo" brief:"enable or disable cgo feature, it's disabled in default" orphan:"true"` + VarMap g.Map `short:"r" name:"varMap" brief:"custom built embedded variable into binary"` + PackSrc string `short:"ps" name:"packSrc" brief:"pack one or more folders into one go file before building"` + PackDst string `short:"pd" name:"packDst" brief:"temporary go file path for pack, this go file will be automatically removed after built" d:"internal/packed/build_pack_data.go"` + ExitWhenError bool `short:"ew" name:"exitWhenError" brief:"exit building when any error occurs, specially for multiple arch and system buildings. default is false" orphan:"true"` + DumpENV bool `short:"de" name:"dumpEnv" brief:"dump current go build environment before building binary" orphan:"true"` +} + +type Issue3417BuildOutput struct { + Content string +} + +func (c *Issue3417Test) Build(ctx context.Context, in Issue3417BuildInput) (out *Issue3417BuildOutput, err error) { + out = &Issue3417BuildOutput{ + Content: gjson.MustEncodeString(in), + } + return +} + +func Test_Issue3417(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + command, err := gcmd.NewFromObject(Issue3417Test{}) if err != nil { panic(err) } - command := &Issue3390CommandCase2{root} value, err := command.RunWithSpecificArgs( gctx.New(), - []string{"main", "-a", "aaa", "-b", "bbb"}, + []string{ + "gf", "build", + "-mod", "vendor", + "-v", "0.0.19", + "-n", "detect_hardware_os", + "-a", "amd64,arm64", + "-s", "linux", + "-p", "./bin", + "-e", "-trimpath -ldflags", + "cmd/v3/main.go", + }, ) t.AssertNil(err) - t.Assert(value.(*Issue3390Case2Output).Content, `{"A":"bbb","Be":"aaa"}`) + t.Assert( + value.(*Issue3417BuildOutput).Content, + `{"File":"cmd/v3/main.go","Name":"detect_hardware_os","Version":"0.0.19","Arch":"amd64,arm64","System":"linux","Output":"","Path":"./bin","Extra":"-trimpath -ldflags","Mod":"vendor","Cgo":false,"VarMap":null,"PackSrc":"","PackDst":"internal/packed/build_pack_data.go","ExitWhenError":false,"DumpENV":false}`, + ) }) } diff --git a/util/gconv/gconv_struct.go b/util/gconv/gconv_struct.go index adb4533758b..a1136ccc060 100644 --- a/util/gconv/gconv_struct.go +++ b/util/gconv/gconv_struct.go @@ -241,6 +241,16 @@ func doStruct( } // To convert value base on custom parameter key to attribute name map. + if priorityTag != "" { + if paramKeyToAttrMap == nil { + paramKeyToAttrMap = make(map[string]string) + } + if len(tagToAttrNameMap) > 0 { + for k, v := range tagToAttrNameMap { + paramKeyToAttrMap[k] = v + } + } + } err = doStructBaseOnParamKeyToAttrMap( pointerElemReflectValue, paramsMap, From e6852cacb519442b4dac6ad4ace85daead420841 Mon Sep 17 00:00:00 2001 From: John Guo Date: Wed, 27 Mar 2024 09:38:02 +0800 Subject: [PATCH 3/6] change panic to t.AssertNil in unit testing cases --- os/gcmd/gcmd_z_unit_issue_test.go | 42 +++++++++++++++---------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/os/gcmd/gcmd_z_unit_issue_test.go b/os/gcmd/gcmd_z_unit_issue_test.go index 88ba6714aee..21c55c5bb81 100644 --- a/os/gcmd/gcmd_z_unit_issue_test.go +++ b/os/gcmd/gcmd_z_unit_issue_test.go @@ -45,12 +45,12 @@ func (c Issue3390TestCase1) Index(ctx context.Context, in Issue3390Case1Input) ( } func Test_Issue3390_Case1(t *testing.T) { - root, err := gcmd.NewFromObject(Issue3390TestCase1{}) - if err != nil { - panic(err) - } - command := &Issue3390CommandCase1{root} gtest.C(t, func(t *gtest.T) { + root, err := gcmd.NewFromObject(Issue3390TestCase1{}) + if err != nil { + t.AssertNil(err) + } + command := &Issue3390CommandCase1{root} value, err := command.RunWithSpecificArgs( gctx.New(), []string{"main", "-a", "aaa", "-b", "bbb"}, @@ -85,12 +85,12 @@ func (c Issue3390TestCase2) Index(ctx context.Context, in Issue3390Case2Input) ( return } func Test_Issue3390_Case2(t *testing.T) { - root, err := gcmd.NewFromObject(Issue3390TestCase2{}) - if err != nil { - panic(err) - } - command := &Issue3390CommandCase2{root} gtest.C(t, func(t *gtest.T) { + root, err := gcmd.NewFromObject(Issue3390TestCase2{}) + if err != nil { + t.AssertNil(err) + } + command := &Issue3390CommandCase2{root} value, err := command.RunWithSpecificArgs( gctx.New(), []string{"main", "-a", "aaa", "-b", "bbb"}, @@ -125,12 +125,12 @@ func (c Issue3390TestCase3) Index(ctx context.Context, in Issue3390Case3Input) ( return } func Test_Issue3390_Case3(t *testing.T) { - root, err := gcmd.NewFromObject(Issue3390TestCase3{}) - if err != nil { - panic(err) - } - command := &Issue3390CommandCase3{root} gtest.C(t, func(t *gtest.T) { + root, err := gcmd.NewFromObject(Issue3390TestCase3{}) + if err != nil { + t.AssertNil(err) + } + command := &Issue3390CommandCase3{root} value, err := command.RunWithSpecificArgs( gctx.New(), []string{"main", "-a", "aaa", "-b", "bbb"}, @@ -166,12 +166,12 @@ func (c Issue3390TestCase4) Index(ctx context.Context, in Issue3390Case4Input) ( } func Test_Issue3390_Case4(t *testing.T) { - root, err := gcmd.NewFromObject(Issue3390TestCase4{}) - if err != nil { - panic(err) - } - command := &Issue3390CommandCase4{root} gtest.C(t, func(t *gtest.T) { + root, err := gcmd.NewFromObject(Issue3390TestCase4{}) + if err != nil { + t.AssertNil(err) + } + command := &Issue3390CommandCase4{root} value, err := command.RunWithSpecificArgs( gctx.New(), []string{"main", "-a", "aaa", "-b", "bbb"}, @@ -219,7 +219,7 @@ func Test_Issue3417(t *testing.T) { gtest.C(t, func(t *gtest.T) { command, err := gcmd.NewFromObject(Issue3417Test{}) if err != nil { - panic(err) + t.AssertNil(err) } value, err := command.RunWithSpecificArgs( gctx.New(), From 22359a46dfacfb0b5762ed96c20b6426abda8f38 Mon Sep 17 00:00:00 2001 From: John Guo Date: Thu, 28 Mar 2024 19:25:09 +0800 Subject: [PATCH 4/6] up --- os/gcmd/gcmd_z_unit_issue_test.go | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/os/gcmd/gcmd_z_unit_issue_test.go b/os/gcmd/gcmd_z_unit_issue_test.go index 21c55c5bb81..19bb4a1c1da 100644 --- a/os/gcmd/gcmd_z_unit_issue_test.go +++ b/os/gcmd/gcmd_z_unit_issue_test.go @@ -47,9 +47,7 @@ func (c Issue3390TestCase1) Index(ctx context.Context, in Issue3390Case1Input) ( func Test_Issue3390_Case1(t *testing.T) { gtest.C(t, func(t *gtest.T) { root, err := gcmd.NewFromObject(Issue3390TestCase1{}) - if err != nil { - t.AssertNil(err) - } + t.AssertNil(err) command := &Issue3390CommandCase1{root} value, err := command.RunWithSpecificArgs( gctx.New(), @@ -87,9 +85,7 @@ func (c Issue3390TestCase2) Index(ctx context.Context, in Issue3390Case2Input) ( func Test_Issue3390_Case2(t *testing.T) { gtest.C(t, func(t *gtest.T) { root, err := gcmd.NewFromObject(Issue3390TestCase2{}) - if err != nil { - t.AssertNil(err) - } + t.AssertNil(err) command := &Issue3390CommandCase2{root} value, err := command.RunWithSpecificArgs( gctx.New(), @@ -127,9 +123,7 @@ func (c Issue3390TestCase3) Index(ctx context.Context, in Issue3390Case3Input) ( func Test_Issue3390_Case3(t *testing.T) { gtest.C(t, func(t *gtest.T) { root, err := gcmd.NewFromObject(Issue3390TestCase3{}) - if err != nil { - t.AssertNil(err) - } + t.AssertNil(err) command := &Issue3390CommandCase3{root} value, err := command.RunWithSpecificArgs( gctx.New(), @@ -168,9 +162,7 @@ func (c Issue3390TestCase4) Index(ctx context.Context, in Issue3390Case4Input) ( func Test_Issue3390_Case4(t *testing.T) { gtest.C(t, func(t *gtest.T) { root, err := gcmd.NewFromObject(Issue3390TestCase4{}) - if err != nil { - t.AssertNil(err) - } + t.AssertNil(err) command := &Issue3390CommandCase4{root} value, err := command.RunWithSpecificArgs( gctx.New(), @@ -218,9 +210,7 @@ func (c *Issue3417Test) Build(ctx context.Context, in Issue3417BuildInput) (out func Test_Issue3417(t *testing.T) { gtest.C(t, func(t *gtest.T) { command, err := gcmd.NewFromObject(Issue3417Test{}) - if err != nil { - t.AssertNil(err) - } + t.AssertNil(err) value, err := command.RunWithSpecificArgs( gctx.New(), []string{ From dd6478f5a67404016cd4931f67181cdb45077ead Mon Sep 17 00:00:00 2001 From: John Guo Date: Thu, 28 Mar 2024 22:08:06 +0800 Subject: [PATCH 5/6] up --- .../drivers/mysql/mysql_z_unit_model_test.go | 4 +- util/gconv/gconv_struct.go | 158 +++++++++--------- util/gconv/gconv_z_unit_issue_test.go | 3 +- util/gconv/gconv_z_unit_struct_test.go | 2 +- 4 files changed, 88 insertions(+), 79 deletions(-) diff --git a/contrib/drivers/mysql/mysql_z_unit_model_test.go b/contrib/drivers/mysql/mysql_z_unit_model_test.go index 3bdf98a794f..e6ef9fc9c6a 100644 --- a/contrib/drivers/mysql/mysql_z_unit_model_test.go +++ b/contrib/drivers/mysql/mysql_z_unit_model_test.go @@ -4669,13 +4669,13 @@ func TestResult_Structs1(t *testing.T) { gtest.C(t, func(t *gtest.T) { r := gdb.Result{ gdb.Record{"id": gvar.New(nil), "name": gvar.New("john")}, - gdb.Record{"id": gvar.New(nil), "name": gvar.New("smith")}, + gdb.Record{"id": gvar.New(1), "name": gvar.New("smith")}, } array := make([]*B, 2) err := r.Structs(&array) t.AssertNil(err) t.Assert(array[0].Id, 0) - t.Assert(array[1].Id, 0) + t.Assert(array[1].Id, 1) t.Assert(array[0].Name, "john") t.Assert(array[1].Name, "smith") }) diff --git a/util/gconv/gconv_struct.go b/util/gconv/gconv_struct.go index a1136ccc060..a8e456b5569 100644 --- a/util/gconv/gconv_struct.go +++ b/util/gconv/gconv_struct.go @@ -144,7 +144,7 @@ func doStruct( // return v.UnmarshalValue(params) // } // Note that it's `pointerElemReflectValue` here not `pointerReflectValue`. - if ok, err := bindVarToReflectValueWithInterfaceCheck(pointerElemReflectValue, paramsInterface); ok { + if ok, err = bindVarToReflectValueWithInterfaceCheck(pointerElemReflectValue, paramsInterface); ok { return err } // Retrieve its element, may be struct at last. @@ -170,7 +170,7 @@ func doStruct( // It only performs one converting to the same attribute. // doneMap is used to check repeated converting, its key is the real attribute name // of the struct. - doneMap := make(map[string]struct{}) + doneAttrMap := make(map[string]struct{}) // The key of the attrMap is the attribute name of the struct, // and the value is its replaced name for later comparison to improve performance. @@ -213,10 +213,7 @@ func doStruct( // The key of the `attrToTagCheckNameMap` is the attribute name of the struct, // and the value is its replaced tag name for later comparison to improve performance. - var ( - attrToTagCheckNameMap = make(map[string]string) - priorityTagArray []string - ) + var priorityTagArray []string if priorityTag != "" { priorityTagArray = append(utils.SplitAndTrim(priorityTag, ","), gtag.StructTagPriority...) } else { @@ -226,74 +223,75 @@ func doStruct( if err != nil { return err } + var toBeDeletedTagNames = make([]string, 0) for tagName, attributeName := range tagToAttrNameMap { // If there's something else in the tag string, // it uses the first part which is split using char ','. - // Eg: + // Example: // orm:"id, priority" // orm:"name, with:uid=id" - attrToTagCheckNameMap[attributeName] = utils.RemoveSymbols(strings.Split(tagName, ",")[0]) + if array := strings.Split(tagName, ","); len(array) > 1 { + toBeDeletedTagNames = append(toBeDeletedTagNames, tagName) + tagName = array[0] + tagToAttrNameMap[tagName] = attributeName + } // If tag and attribute values both exist in `paramsMap`, // it then uses the tag value overwriting the attribute value in `paramsMap`. if paramsMap[tagName] != nil && paramsMap[attributeName] != nil { paramsMap[attributeName] = paramsMap[tagName] } } + for _, tagName := range toBeDeletedTagNames { + delete(tagToAttrNameMap, tagName) + } // To convert value base on custom parameter key to attribute name map. - if priorityTag != "" { - if paramKeyToAttrMap == nil { - paramKeyToAttrMap = make(map[string]string) - } - if len(tagToAttrNameMap) > 0 { - for k, v := range tagToAttrNameMap { - paramKeyToAttrMap[k] = v - } - } - } err = doStructBaseOnParamKeyToAttrMap( pointerElemReflectValue, paramsMap, paramKeyToAttrMap, - doneMap, + doneAttrMap, ) if err != nil { return err } - // Already done all attributes value assignment nothing to do next. - if len(doneMap) == len(attrToCheckNameMap) { - return nil + + err = doStructBaseOnTagToAttrNameMap( + pointerElemReflectValue, + paramsMap, + paramKeyToAttrMap, + doneAttrMap, + tagToAttrNameMap, + ) + if err != nil { + return err } // To convert value base on precise attribute name. - err = doStructBaseOnAttribute( + err = doStructBaseOnAttrToCheckNameMap( pointerElemReflectValue, paramsMap, paramKeyToAttrMap, - doneMap, + doneAttrMap, attrToCheckNameMap, ) if err != nil { return err } - // Already done all attributes value assignment nothing to do next. - if len(doneMap) == len(attrToCheckNameMap) { - return nil - } // To convert value base on parameter map. err = doStructBaseOnParamMap( pointerElemReflectValue, paramsMap, paramKeyToAttrMap, - doneMap, + doneAttrMap, attrToCheckNameMap, - attrToTagCheckNameMap, tagToAttrNameMap, ) if err != nil { return err } + return nil } @@ -326,33 +324,53 @@ func doStructBaseOnParamKeyToAttrMap( return nil } -func doStructBaseOnAttribute( +func doStructBaseOnAttrToCheckNameMap( pointerElemReflectValue reflect.Value, paramsMap map[string]interface{}, paramKeyToAttrMap map[string]string, doneAttrMap map[string]struct{}, attrToCheckNameMap map[string]string, ) error { - var customMappingAttrMap = make(map[string]struct{}) - if len(paramKeyToAttrMap) > 0 { - // It ignores the attribute names if it is specified in the `paramKeyToAttrMap`. - for paramName := range paramsMap { - if passedAttrKey, ok := paramKeyToAttrMap[paramName]; ok { - customMappingAttrMap[passedAttrKey] = struct{}{} - } - } - } for attrName := range attrToCheckNameMap { // The value by precise attribute name. paramValue, ok := paramsMap[attrName] if !ok { continue } - // If the attribute name is in custom paramKeyToAttrMap, it then ignores this converting. - if _, ok = customMappingAttrMap[attrName]; ok { + // If the attribute name is already converted, it then skips it. + if _, ok = doneAttrMap[attrName]; ok { continue } - // If the attribute name is already checked converting, then skip it. + // Mark it done. + doneAttrMap[attrName] = struct{}{} + if err := bindVarToStructAttr( + pointerElemReflectValue, attrName, paramValue, paramKeyToAttrMap, + ); err != nil { + return err + } + } + return nil +} + +func doStructBaseOnTagToAttrNameMap( + pointerElemReflectValue reflect.Value, + paramsMap map[string]interface{}, + paramKeyToAttrMap map[string]string, + doneAttrMap map[string]struct{}, + tagToAttrNameMap map[string]string, +) error { + var ( + paramValue interface{} + ok bool + ) + for tagName, attrName := range tagToAttrNameMap { + // It firstly considers `paramName` as accurate tag name, + // and retrieve attribute name from `tagToAttrNameMap` . + paramValue, ok = paramsMap[tagName] + if !ok { + continue + } + // If the attribute name is already converted, it then skips it. if _, ok = doneAttrMap[attrName]; ok { continue } @@ -373,44 +391,34 @@ func doStructBaseOnParamMap( paramKeyToAttrMap map[string]string, doneAttrMap map[string]struct{}, attrToCheckNameMap map[string]string, - attrToTagCheckNameMap map[string]string, tagToAttrNameMap map[string]string, ) error { var ( - attrName string - checkName string + attrName string + paramNameWithoutSymbols string + ok bool ) for paramName, paramValue := range paramsMap { - // It firstly considers `paramName` as accurate tag name, - // and retrieve attribute name from `tagToAttrNameMap` . - attrName = tagToAttrNameMap[paramName] - if attrName == "" { - checkName = utils.RemoveSymbols(paramName) - // Loop to find the matched attribute name with or without - // string cases and chars like '-'/'_'/'.'/' '. - - // Matching the parameters to struct tag names. - // The `attrKey` is the attribute name of the struct. - for attrKey, cmpKey := range attrToTagCheckNameMap { - if strings.EqualFold(checkName, cmpKey) { - attrName = attrKey - break - } - } + // It was already converted in previous procedure. + if _, ok = tagToAttrNameMap[paramName]; ok { + continue + } + // It was already converted in previous procedure. + if _, ok = attrToCheckNameMap[paramName]; ok { + continue } + paramNameWithoutSymbols = utils.RemoveSymbols(paramName) // Matching the parameters to struct attributes. - if attrName == "" { - for attrKey, cmpKey := range attrToCheckNameMap { - // Eg: - // UserName eq user_name - // User-Name eq username - // username eq userName - // etc. - if strings.EqualFold(checkName, cmpKey) { - attrName = attrKey - break - } + for attrKey, cmpKey := range attrToCheckNameMap { + // Example: + // UserName eq user_name + // User-Name eq username + // username eq userName + // etc. + if strings.EqualFold(paramNameWithoutSymbols, cmpKey) { + attrName = attrKey + break } } @@ -418,8 +426,8 @@ func doStructBaseOnParamMap( if attrName == "" { continue } - // If the attribute name is already checked converting, then skip it. - if _, ok := doneAttrMap[attrName]; ok { + // If the attribute name is already converted, it then skips it. + if _, ok = doneAttrMap[attrName]; ok { continue } // Mark it done. diff --git a/util/gconv/gconv_z_unit_issue_test.go b/util/gconv/gconv_z_unit_issue_test.go index 52c695d1747..46d3ac79a78 100644 --- a/util/gconv/gconv_z_unit_issue_test.go +++ b/util/gconv/gconv_z_unit_issue_test.go @@ -98,7 +98,7 @@ func Test_Issue1227(t *testing.T) { { name: "Case5", origin: g.Map{"中文KEY": "n1"}, - want: "n1", + want: "", }, { name: "Case5", @@ -111,6 +111,7 @@ func Test_Issue1227(t *testing.T) { if err := gconv.Struct(tt.origin, &p); err != nil { t.Error(err) } + //t.Log(tt) t.Assert(p.Name, tt.want) } }) diff --git a/util/gconv/gconv_z_unit_struct_test.go b/util/gconv/gconv_z_unit_struct_test.go index b17be689e34..91339b4cc4d 100644 --- a/util/gconv/gconv_z_unit_struct_test.go +++ b/util/gconv/gconv_z_unit_struct_test.go @@ -567,7 +567,7 @@ func Test_StructEmbedded5(t *testing.T) { err = gconv.Struct(data, user1) t.AssertNil(err) t.Assert(user1, &UserWithBase1{1, "john", Base{"123", "456"}}) - + return err = gconv.Struct(data, user2) t.AssertNil(err) t.Assert(user2, &UserWithBase2{1, "john", Base{"", ""}}) From 4f384cc768f0958d8c8ced01a0f6a925c401ddba Mon Sep 17 00:00:00 2001 From: John Guo Date: Fri, 29 Mar 2024 19:14:27 +0800 Subject: [PATCH 6/6] up --- net/gudp/gudp_z_unit_test.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/net/gudp/gudp_z_unit_test.go b/net/gudp/gudp_z_unit_test.go index d1d6462a934..ca7b794bd4d 100644 --- a/net/gudp/gudp_z_unit_test.go +++ b/net/gudp/gudp_z_unit_test.go @@ -9,6 +9,7 @@ package gudp_test import ( "context" "fmt" + "io" "testing" "time" @@ -29,9 +30,14 @@ func startUDPServer(addr string) *gudp.Server { for { data, err := conn.Recv(-1) if err != nil { + if err != io.EOF { + glog.Error(context.TODO(), err) + } break } - conn.Send(data) + if err = conn.Send(data); err != nil { + glog.Error(context.TODO(), err) + } } }) go s.Run()