diff --git a/os/gcmd/gcmd_command.go b/os/gcmd/gcmd_command.go index 2be2c7237..11f500fd2 100644 --- a/os/gcmd/gcmd_command.go +++ b/os/gcmd/gcmd_command.go @@ -35,24 +35,24 @@ type Function func(ctx context.Context, parser *Parser) (err error) // Option is the command value that is specified by a name or shor name. // An Option can have or have no value bound to it. type Option struct { - Name string // Option name. - Short string // Option short. - Brief string // Brief info about this Option, which is used in help info. - NeedValue bool // Whether this Option having or having no value bound to it. + Name string // Option name. + Short string // Option short. + Brief string // Brief info about this Option, which is used in help info. + Orphan bool // Whether this Option having or having no value bound to it. } var ( // defaultHelpOption is the default help option that will be automatically added to each command. defaultHelpOption = Option{ - Name: `help`, - Short: `h`, - Brief: `more information about this command`, - NeedValue: false, + Name: `help`, + Short: `h`, + Brief: `more information about this command`, + Orphan: true, } ) -// Add adds one or more sub-commands to current command. -func (c *Command) Add(commands ...Command) error { +// AddCommand adds one or more sub-commands to current command. +func (c *Command) AddCommand(commands ...Command) error { for _, cmd := range commands { cmd.Name = gstr.Trim(cmd.Name) if cmd.Name == "" { diff --git a/os/gcmd/gcmd_command_object.go b/os/gcmd/gcmd_command_object.go new file mode 100644 index 000000000..9b569f474 --- /dev/null +++ b/os/gcmd/gcmd_command_object.go @@ -0,0 +1,119 @@ +// 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. +// + +package gcmd + +import ( + "reflect" + + "github.com/gogf/gf/v2/errors/gcode" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/internal/utils" + "github.com/gogf/gf/v2/text/gstr" + "github.com/gogf/gf/v2/util/gmeta" +) + +const ( + tagNameName = `name` + tagNameUsage = `usage` + tagNameBrief = `brief` + tagNameShort = `short` + tagNameOrphan = `orphan` + tagNameDescription = `description` + tagNameDc = `dc` + tagNameAddition = `additional` + tagNameAd = `ad` +) + +func (c *Command) AddObject(objects ...interface{}) (err error) { + for _, object := range objects { + if err = c.doAddObject(object); err != nil { + return err + } + } + return nil +} + +func (c *Command) doAddObject(object interface{}) error { + originValueAndKind := utils.OriginValueAndKind(object) + if originValueAndKind.OriginKind != reflect.Struct { + return gerror.Newf( + `input object should be type of struct, but got "%s"`, + originValueAndKind.InputValue.Type().String(), + ) + } + for i := 0; i < originValueAndKind.InputValue.NumMethod(); i++ { + method := originValueAndKind.InputValue.Method(i) + } + for _, field := range fields { + + } + return nil +} + +func newCommandFromMethod(object, method reflect.Value) (cmd *Command, err error) { + var ( + reflectType = method.Type() + ) + if reflectType.NumIn() != 2 || reflectType.NumOut() != 2 { + if reflectType.PkgPath() != "" { + err = gerror.NewCodef( + gcode.CodeInvalidParameter, + `invalid handler: %s.%s.%s defined as "%s", but "func(context.Context, Input)(Output, error)" is required`, + reflectType.PkgPath(), object.Type().Name(), reflectType.Name(), reflectType.String(), + ) + } else { + err = gerror.NewCodef( + gcode.CodeInvalidParameter, + `invalid handler: defined as "%s", but "func(context.Context, Input)(Output, error)" is required`, + reflectType.String(), + ) + } + return + } + + if reflectType.In(0).String() != "context.Context" { + err = gerror.NewCodef( + gcode.CodeInvalidParameter, + `invalid handler: defined as "%s", but the first input parameter should be type of "context.Context"`, + reflectType.String(), + ) + return + } + + if reflectType.Out(1).String() != "error" { + err = gerror.NewCodef( + gcode.CodeInvalidParameter, + `invalid handler: defined as "%s", but the last output parameter should be type of "error"`, + reflectType.String(), + ) + return + } + + // The input struct should be named as `xxxInput`. + if !gstr.HasSuffix(reflectType.In(1).String(), `Input`) { + err = gerror.NewCodef( + gcode.CodeInvalidParameter, + `invalid struct naming for input: defined as "%s", but it should be named with "Input" suffix like "xxxInput"`, + reflectType.In(1).String(), + ) + return + } + + // The output struct should be named as `xxxOutput`. + if !gstr.HasSuffix(reflectType.Out(0).String(), `Output`) { + err = gerror.NewCodef( + gcode.CodeInvalidParameter, + `invalid struct naming for output: defined as "%s", but it should be named with "Output" suffix like "xxxOutput"`, + reflectType.Out(0).String(), + ) + return + } + var ( + metaMap = gmeta.Data() + ) +} diff --git a/os/gcmd/gcmd_command_run.go b/os/gcmd/gcmd_command_run.go index 391cf3c15..0a989eaf8 100644 --- a/os/gcmd/gcmd_command_run.go +++ b/os/gcmd/gcmd_command_run.go @@ -84,7 +84,7 @@ func (c *Command) reParse(ctx context.Context, parser *Parser) (*Parser, error) } else { optionKey = option.Name } - supportedOptions[optionKey] = option.NeedValue + supportedOptions[optionKey] = !option.Orphan } return Parse(supportedOptions) } diff --git a/os/gcmd/gcmd_z_unit_feature_object_test.go b/os/gcmd/gcmd_z_unit_feature_object_test.go new file mode 100644 index 000000000..1af5dba9e --- /dev/null +++ b/os/gcmd/gcmd_z_unit_feature_object_test.go @@ -0,0 +1,113 @@ +// 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" + "fmt" + "testing" + + "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 TestCmdObject struct{} + +type TestCmdObjectInput struct { + g.Meta `name:"gf" usage:"gf env/test" brief:"gf env command" dc:"description" ad:"ad"` +} +type TestCmdObjectOutput struct{} + +type TestCmdObjectEnvInput struct { + g.Meta `name:"env" usage:"gf env/test" brief:"gf env command" dc:"description" ad:"ad"` + Name string `v:"required" name:"name" short:"n" orphan:"false" brief:"name for command"` +} +type TestCmdObjectEnvOutput struct{} + +type TestCmdObjectTestInput struct { + g.Meta `name:"test" usage:"gf env/test" brief:"gf test command" dc:"description" ad:"ad"` + Name string `v:"required" name:"name" short:"n" orphan:"false" brief:"name for command"` +} +type TestCmdObjectTestOutput struct{} + +func (TestCmdObject) Root(ctx context.Context, in TestCmdObjectInput) (out *TestCmdObjectOutput, err error) { + return +} + +func (TestCmdObject) Env(ctx context.Context, in TestCmdObjectEnvInput) (out *TestCmdObjectEnvOutput, err error) { + return +} + +func (TestCmdObject) Test(ctx context.Context, in TestCmdObjectTestInput) (out *TestCmdObjectTestOutput, err error) { + return +} + +func Test_Command_AddObject(t *testing.T) { + + gtest.C(t, func(t *gtest.T) { + var ( + ctx = gctx.New() + err error + ) + commandRoot := &gcmd.Command{ + Name: "gf", + } + // env + commandEnv := gcmd.Command{ + Name: "env", + Func: func(ctx context.Context, parser *gcmd.Parser) error { + fmt.Println("env") + return nil + }, + } + // test + commandTest := gcmd.Command{ + Name: "test", + Brief: "test brief", + Description: "test description current Golang environment variables", + Examples: ` +gf get github.com/gogf/gf +gf get github.com/gogf/gf@latest +gf get github.com/gogf/gf@master +gf get golang.org/x/sys +`, + Options: []gcmd.Option{ + { + Name: "my-option", + Short: "o", + Brief: "It's my custom option", + Orphan: false, + }, + { + Name: "another", + Short: "a", + Brief: "It's my another custom option", + Orphan: false, + }, + }, + Func: func(ctx context.Context, parser *gcmd.Parser) error { + fmt.Println("test") + return nil + }, + } + err = commandRoot.AddCommand( + commandEnv, + commandTest, + ) + if err != nil { + g.Log().Fatal(ctx, err) + } + + if err = commandRoot.Run(ctx); err != nil { + g.Log().Fatal(ctx, err) + } + }) +} diff --git a/os/gcmd/gcmd_z_unit_test.go b/os/gcmd/gcmd_z_unit_test.go index 02b2f6673..e8245cb79 100644 --- a/os/gcmd/gcmd_z_unit_test.go +++ b/os/gcmd/gcmd_z_unit_test.go @@ -101,16 +101,16 @@ gf get golang.org/x/sys `, Options: []gcmd.Option{ { - Name: "my-option", - Short: "o", - Brief: "It's my custom option", - NeedValue: false, + Name: "my-option", + Short: "o", + Brief: "It's my custom option", + Orphan: true, }, { - Name: "another", - Short: "a", - Brief: "It's my another custom option", - NeedValue: false, + Name: "another", + Short: "a", + Brief: "It's my another custom option", + Orphan: true, }, }, Func: func(ctx context.Context, parser *gcmd.Parser) error { @@ -118,7 +118,7 @@ gf get golang.org/x/sys return nil }, } - err = commandRoot.Add( + err = commandRoot.AddCommand( commandEnv, commandTest, ) @@ -153,7 +153,7 @@ Use 'gf help COMMAND' or 'gf COMMAND -h' for detail about a command, which has ' return nil }, } - if err = c.Add(commandEnv); err != nil { + if err = c.AddCommand(commandEnv); err != nil { g.Log().Fatal(ctx, err) } // get @@ -172,7 +172,7 @@ gf get golang.org/x/sys return nil }, } - if err = c.Add(commandGet); err != nil { + if err = c.AddCommand(commandGet); err != nil { g.Log().Fatal(ctx, err) } // build @@ -212,7 +212,7 @@ gf build main.go -n my-app -v 1.0 -a amd64,386 -s linux,windows,darwin -p ./dock return nil }, } - if err = c.Add(commandBuild); err != nil { + if err = c.AddCommand(commandBuild); err != nil { g.Log().Fatal(ctx, err) } c.Run(ctx)