gf/os/gcmd/gcmd_command_object.go

347 lines
9.3 KiB
Go
Raw Normal View History

2021-11-22 11:23:46 +08:00
// 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 (
"context"
2021-11-22 11:23:46 +08:00
"reflect"
"github.com/gogf/gf/v2/container/gset"
2021-11-22 11:23:46 +08:00
"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/os/gstructs"
2021-11-22 11:23:46 +08:00
"github.com/gogf/gf/v2/text/gstr"
2021-11-23 14:08:37 +08:00
"github.com/gogf/gf/v2/util/gconv"
2021-11-22 11:23:46 +08:00
"github.com/gogf/gf/v2/util/gmeta"
2021-11-23 14:08:37 +08:00
"github.com/gogf/gf/v2/util/gutil"
"github.com/gogf/gf/v2/util/gvalid"
2021-11-22 11:23:46 +08:00
)
const (
tagNameDc = `dc`
tagNameAd = `ad`
2021-12-04 00:26:45 +08:00
tagNameEg = `eg`
2021-12-09 23:49:39 +08:00
tagNameArg = `arg`
tagNameRoot = `root`
2021-11-22 11:23:46 +08:00
)
var (
// defaultValueTags is the struct tag names for default value storing.
defaultValueTags = []string{"d", "default"}
)
// NewFromObject creates and returns a root command object using given object.
2021-12-07 22:10:19 +08:00
func NewFromObject(object interface{}) (rootCmd *Command, err error) {
2021-11-22 11:23:46 +08:00
originValueAndKind := utils.OriginValueAndKind(object)
if originValueAndKind.OriginKind != reflect.Struct {
err = gerror.Newf(
2021-11-22 11:23:46 +08:00
`input object should be type of struct, but got "%s"`,
originValueAndKind.InputValue.Type().String(),
)
return
2021-11-22 11:23:46 +08:00
}
// Root command creating.
rootCmd, err = newCommandFromObjectMeta(object)
if err != nil {
return
}
// Sub command creating.
var (
nameSet = gset.NewStrSet()
rootCommandName = gmeta.Get(object, tagNameRoot).String()
2021-12-07 22:10:19 +08:00
subCommands []*Command
)
2021-12-10 23:58:10 +08:00
if rootCommandName == "" {
rootCommandName = rootCmd.Name
}
for i := 0; i < originValueAndKind.InputValue.NumMethod(); i++ {
var (
method = originValueAndKind.InputValue.Method(i)
2021-12-07 22:10:19 +08:00
methodCommand *Command
)
methodCommand, err = newCommandFromMethod(object, method)
if err != nil {
return
}
if nameSet.Contains(methodCommand.Name) {
err = gerror.Newf(
`command name should be unique, found duplicated command name in method "%s"`,
method.Type().String(),
)
return
}
if rootCommandName == methodCommand.Name {
if rootCmd.Func == nil {
rootCmd.Func = methodCommand.Func
}
if rootCmd.FuncWithValue == nil {
rootCmd.FuncWithValue = methodCommand.FuncWithValue
}
2021-12-09 23:49:39 +08:00
if len(rootCmd.Arguments) == 0 {
rootCmd.Arguments = methodCommand.Arguments
}
} else {
subCommands = append(subCommands, methodCommand)
}
}
if len(subCommands) > 0 {
err = rootCmd.AddCommand(subCommands...)
}
2021-11-23 14:08:37 +08:00
return
2021-11-22 11:23:46 +08:00
}
2021-12-07 22:10:19 +08:00
func newCommandFromObjectMeta(object interface{}) (command *Command, err error) {
var (
metaData = gmeta.Data(object)
)
if len(metaData) == 0 {
err = gerror.Newf(
`no meta data found in struct "%s"`,
reflect.TypeOf(object).String(),
)
return
}
if err = gconv.Scan(metaData, &command); err != nil {
return
}
// Name filed is necessary.
if command.Name == "" {
err = gerror.Newf(
`command name cannot be empty, "name" tag not found in meta of struct "%s"`,
reflect.TypeOf(object).String(),
)
return
}
if command.Description == "" {
command.Description = metaData[tagNameDc]
}
2021-12-04 00:26:45 +08:00
if command.Examples == "" {
command.Examples = metaData[tagNameEg]
}
if command.Additional == "" {
command.Additional = metaData[tagNameAd]
}
return
}
2021-12-07 22:10:19 +08:00
func newCommandFromMethod(object interface{}, method reflect.Value) (command *Command, err error) {
2021-11-22 11:23:46 +08:00
var (
reflectType = method.Type()
)
2021-11-23 14:08:37 +08:00
// Necessary validation for input/output parameters and naming.
2021-11-22 11:23:46 +08:00
if reflectType.NumIn() != 2 || reflectType.NumOut() != 2 {
if reflectType.PkgPath() != "" {
err = gerror.NewCodef(
gcode.CodeInvalidParameter,
`invalid command: %s.%s.%s defined as "%s", but "func(context.Context, Input)(Output, error)" is required`,
reflectType.PkgPath(), reflect.TypeOf(object).Name(), reflectType.Name(), reflectType.String(),
2021-11-22 11:23:46 +08:00
)
} else {
err = gerror.NewCodef(
gcode.CodeInvalidParameter,
`invalid command: defined as "%s", but "func(context.Context, Input)(Output, error)" is required`,
2021-11-22 11:23:46 +08:00
reflectType.String(),
)
}
return
2021-11-22 11:23:46 +08:00
}
if reflectType.In(0).String() != "context.Context" {
err = gerror.NewCodef(
gcode.CodeInvalidParameter,
`invalid command: defined as "%s", but the first input parameter should be type of "context.Context"`,
2021-11-22 11:23:46 +08:00
reflectType.String(),
)
return
2021-11-22 11:23:46 +08:00
}
if reflectType.Out(1).String() != "error" {
err = gerror.NewCodef(
gcode.CodeInvalidParameter,
`invalid command: defined as "%s", but the last output parameter should be type of "error"`,
2021-11-22 11:23:46 +08:00
reflectType.String(),
)
return
2021-11-22 11:23:46 +08:00
}
// 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
2021-11-22 11:23:46 +08:00
}
// 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
2021-11-23 14:08:37 +08:00
}
var (
inputObject reflect.Value
2021-11-23 14:08:37 +08:00
)
if method.Type().In(1).Kind() == reflect.Ptr {
inputObject = reflect.New(method.Type().In(1).Elem()).Elem()
} else {
inputObject = reflect.New(method.Type().In(1)).Elem()
}
// Command creating.
if command, err = newCommandFromObjectMeta(inputObject.Interface()); err != nil {
return
}
// Options creating.
2021-12-09 23:49:39 +08:00
if command.Arguments, err = newArgumentsFromInput(inputObject.Interface()); err != nil {
return
2021-11-22 11:23:46 +08:00
}
2021-12-09 23:49:39 +08:00
// =============================================================================================
// Create function that has value return.
2021-12-09 23:49:39 +08:00
// =============================================================================================
command.FuncWithValue = func(ctx context.Context, parser *Parser) (out interface{}, err error) {
2021-12-02 23:50:29 +08:00
ctx = context.WithValue(ctx, CtxKeyParser, parser)
2021-11-24 21:57:41 +08:00
defer func() {
if exception := recover(); exception != nil {
if v, ok := exception.(error); ok && gerror.HasStack(v) {
err = v
} else {
err = gerror.New(`exception recovered:` + gconv.String(exception))
}
}
}()
var (
data = gconv.Map(parser.GetOptAll())
2021-12-09 23:49:39 +08:00
argIndex = 0
arguments = gconv.Strings(ctx.Value(CtxKeyArguments))
inputValues = []reflect.Value{reflect.ValueOf(ctx)}
)
if data == nil {
data = map[string]interface{}{}
}
2021-11-24 21:41:36 +08:00
// Handle orphan options.
2021-12-09 23:49:39 +08:00
for _, arg := range command.Arguments {
if arg.IsArg {
// Read argument from command line index.
if argIndex < len(arguments) {
data[arg.Name] = arguments[argIndex]
argIndex++
}
} else {
// Read argument from command line option name.
if arg.Orphan && parser.ContainsOpt(arg.Name) {
data[arg.Name] = "true"
}
2021-11-24 21:41:36 +08:00
}
}
// Default values from struct tag.
2021-11-24 21:57:41 +08:00
if err = mergeDefaultStructValue(data, inputObject.Interface()); err != nil {
return nil, err
}
// Construct input parameters.
if len(data) > 0 {
2021-11-24 21:41:36 +08:00
if inputObject.Kind() == reflect.Ptr {
err = gconv.Scan(data, inputObject.Interface())
} else {
err = gconv.Struct(data, inputObject.Addr().Interface())
}
if err != nil {
return
}
}
// Parameters validation.
if err = gvalid.New().Bail().Data(inputObject.Interface()).Assoc(data).Run(ctx); err != nil {
2021-11-30 16:43:42 +08:00
err = gerror.Wrap(gerror.Current(err), `validation failed for command options`)
return
}
inputValues = append(inputValues, inputObject)
// Call handler with dynamic created parameter values.
results := method.Call(inputValues)
out = results[0].Interface()
if !results[1].IsNil() {
if v, ok := results[1].Interface().(error); ok {
err = v
}
}
return
2021-11-23 14:08:37 +08:00
}
return
}
2021-11-23 14:08:37 +08:00
2021-12-09 23:49:39 +08:00
func newArgumentsFromInput(object interface{}) (args []Argument, err error) {
2021-11-24 22:27:09 +08:00
var (
fields []gstructs.Field
)
fields, err = gstructs.Fields(gstructs.FieldsInput{
Pointer: object,
RecursiveOption: gstructs.RecursiveOptionEmbeddedNoTag,
})
for _, field := range fields {
var (
2021-12-09 23:49:39 +08:00
arg = Argument{}
2021-11-24 22:27:09 +08:00
metaData = field.TagMap()
)
2021-12-09 23:49:39 +08:00
if err = gconv.Scan(metaData, &arg); err != nil {
2021-11-24 22:27:09 +08:00
return nil, err
}
2021-12-09 23:49:39 +08:00
if arg.Name == "" {
arg.Name = field.Name()
2021-11-24 22:27:09 +08:00
}
2021-12-09 23:49:39 +08:00
if arg.Name == helpOptionName {
2021-11-24 22:27:09 +08:00
return nil, gerror.Newf(
`option name "%s" is already token by built-in options`,
2021-12-09 23:49:39 +08:00
arg.Name,
2021-11-24 22:27:09 +08:00
)
}
2021-12-09 23:49:39 +08:00
if arg.Short == helpOptionNameShort {
2021-11-24 22:27:09 +08:00
return nil, gerror.Newf(
`short option name "%s" is already token by built-in options`,
2021-12-09 23:49:39 +08:00
arg.Short,
2021-11-24 22:27:09 +08:00
)
}
2021-12-09 23:49:39 +08:00
if v, ok := metaData[tagNameArg]; ok {
arg.IsArg = gconv.Bool(v)
}
args = append(args, arg)
2021-11-24 22:27:09 +08:00
}
return
}
// mergeDefaultStructValue merges the request parameters with default values from struct tag definition.
func mergeDefaultStructValue(data map[string]interface{}, pointer interface{}) error {
tagFields, err := gstructs.TagFields(pointer, defaultValueTags)
if err != nil {
return err
}
if len(tagFields) > 0 {
var (
foundKey string
foundValue interface{}
)
for _, field := range tagFields {
foundKey, foundValue = gutil.MapPossibleItemByKey(data, field.Name())
if foundKey == "" {
data[field.Name()] = field.TagValue
} else {
if utils.IsEmpty(foundValue) {
data[foundKey] = field.TagValue
}
}
}
2021-11-23 14:08:37 +08:00
}
return nil
2021-11-23 14:08:37 +08:00
}