This commit is contained in:
John Guo
2023-11-22 21:05:39 +08:00
committed by GitHub
parent b1754f8254
commit ea5d52cb2f
11 changed files with 187 additions and 97 deletions

View File

@ -55,6 +55,7 @@ func (c cGF) Index(ctx context.Context, in cGFInput) (out *cGFOutput, err error)
_, err = Version.Index(ctx, cVersionInput{})
return
}
answer := "n"
// No argument or option, do installation checks.
if data, isInstalled := service.Install.IsInstalled(); !isInstalled {
@ -71,6 +72,7 @@ func (c cGF) Index(ctx context.Context, in cGFInput) (out *cGFOutput, err error)
gcmd.Scan("press `Enter` to exit...")
return
}
// Print help content.
gcmd.CommandFromCtx(ctx).Print()
return

View File

@ -28,6 +28,8 @@ const (
helpOptionNameShort = "h"
maxLineChars = 120
tracingInstrumentName = "github.com/gogf/gf/v2/os/gcmd.Command"
tagNameName = "name"
tagNameShort = "short"
)
// Init does custom initialization.

View File

@ -42,12 +42,11 @@ type FuncWithValue func(ctx context.Context, parser *Parser) (out interface{}, e
// Argument is the command value that are used by certain command.
type Argument struct {
Name string // Option name.
FieldName string // Option field name.
Short string // Option short.
Brief string // Brief info about this Option, which is used in help info.
IsArg bool // IsArg marks this argument taking value from command line argument instead of option.
Orphan 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.
IsArg bool // IsArg marks this argument taking value from command line argument instead of option.
Orphan bool // Whether this Option having or having no value bound to it.
}
var (

View File

@ -353,9 +353,6 @@ func newArgumentsFromInput(object interface{}) (args []Argument, err error) {
}
if arg.Name == "" {
arg.Name = field.Name()
} else if arg.Name != field.Name() {
arg.FieldName = field.Name()
nameSet.Add(arg.FieldName)
}
if arg.Name == helpOptionName {
return nil, gerror.Newf(
@ -411,14 +408,25 @@ func mergeDefaultStructValue(data map[string]interface{}, pointer interface{}) e
foundValue interface{}
)
for _, field := range tagFields {
var (
nameValue = field.Tag(tagNameName)
shortValue = field.Tag(tagNameShort)
)
// If it already has value, it then ignores the default value.
if value, ok := data[nameValue]; ok {
data[field.Name()] = value
continue
}
if value, ok := data[shortValue]; ok {
data[field.Name()] = value
continue
}
foundKey, foundValue = gutil.MapPossibleItemByKey(data, field.Name())
if foundKey == "" {
data[field.Name()] = field.TagValue
} else {
if utils.IsEmpty(foundValue) {
data[foundKey] = field.TagValue
} else {
data[field.Name()] = foundValue
}
}
}

View File

@ -171,12 +171,10 @@ func (c *Command) reParse(ctx context.Context, parser *Parser) (*Parser, error)
if arg.IsArg {
continue
}
optionKey = arg.Name
if arg.FieldName != "" {
optionKey += fmt.Sprintf(`,%s`, arg.FieldName)
}
if arg.Short != "" {
optionKey += fmt.Sprintf(`,%s`, arg.Short)
optionKey = fmt.Sprintf(`%s,%s`, arg.Name, arg.Short)
} else {
optionKey = arg.Name
}
supportedOptions[optionKey] = !arg.Orphan
}

View File

@ -30,12 +30,14 @@ type TestCmdObjectEnvInput struct {
type TestCmdObjectEnvOutput struct{}
type TestCmdObjectTestInput struct {
g.Meta `name:"test" usage:"root test" brief:"root test command" dc:"root test command description" ad:"root test command ad"`
Name string `name:"yourname" v:"required" short:"n" orphan:"false" brief:"name for test command" d:"tom"`
g.Meta `name:"test" usage:"root test" brief:"root test command" dc:"root test command description" ad:"root test command ad"`
Name string `name:"yourname" v:"required" short:"n" orphan:"false" brief:"name for test command" d:"tom"`
Version bool `name:"version" short:"v" orphan:"true" brief:"show version"`
}
type TestCmdObjectTestOutput struct {
Content string
Name string
Version bool
}
func (TestCmdObject) Env(ctx context.Context, in TestCmdObjectEnvInput) (out *TestCmdObjectEnvOutput, err error) {
@ -44,7 +46,8 @@ func (TestCmdObject) Env(ctx context.Context, in TestCmdObjectEnvInput) (out *Te
func (TestCmdObject) Test(ctx context.Context, in TestCmdObjectTestInput) (out *TestCmdObjectTestOutput, err error) {
out = &TestCmdObjectTestOutput{
Content: in.Name,
Name: in.Name,
Version: in.Version,
}
return
}
@ -93,19 +96,25 @@ func Test_Command_NewFromObject_RunWithValue(t *testing.T) {
os.Args = []string{"root", "test", "-n=john"}
value, err := cmd.RunWithValueError(ctx)
t.AssertNil(err)
t.Assert(value, `{"Content":"john"}`)
t.Assert(value, `{"Name":"john","Version":false}`)
// test name tag name
os.Args = []string{"root", "test", "-yourname=hailaz"}
value1, err1 := cmd.RunWithValueError(ctx)
t.AssertNil(err1)
t.Assert(value1, `{"Content":"hailaz"}`)
t.Assert(value1, `{"Name":"hailaz","Version":false}`)
// test default tag value
os.Args = []string{"root", "test"}
value2, err2 := cmd.RunWithValueError(ctx)
t.AssertNil(err2)
t.Assert(value2, `{"Content":"tom"}`)
t.Assert(value2, `{"Name":"tom","Version":false}`)
// test name tag and orphan tag true
os.Args = []string{"root", "test", "-v"}
value3, err3 := cmd.RunWithValueError(ctx)
t.AssertNil(err3)
t.Assert(value3, `{"Name":"tom","Version":true}`)
})
}
@ -123,7 +132,7 @@ func Test_Command_AddObject(t *testing.T) {
os.Args = []string{"start", "root", "test", "-n=john"}
value, err := command.RunWithValueError(ctx)
t.AssertNil(err)
t.Assert(value, `{"Content":"john"}`)
t.Assert(value, `{"Name":"john","Version":false}`)
})
}

View File

@ -28,7 +28,7 @@ func MapToMaps(params interface{}, pointer interface{}, mapping ...map[string]st
//
// The optional parameter `mapping` is used for struct attribute to map key mapping, which makes
// sense only if the item of `params` is type struct.
func doMapToMaps(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) {
func doMapToMaps(params interface{}, pointer interface{}, paramKeyToAttrMap ...map[string]string) (err error) {
// If given `params` is JSON, it then uses json.Unmarshal doing the converting.
switch r := params.(type) {
case []byte:
@ -124,13 +124,13 @@ func doMapToMaps(params interface{}, pointer interface{}, mapping ...map[string]
var item reflect.Value
if pointerElemType.Kind() == reflect.Ptr {
item = reflect.New(pointerElemType.Elem())
if err = MapToMap(paramsRv.Index(i).Interface(), item, mapping...); err != nil {
if err = MapToMap(paramsRv.Index(i).Interface(), item, paramKeyToAttrMap...); err != nil {
return err
}
pointerSlice.Index(i).Set(item)
} else {
item = reflect.New(pointerElemType)
if err = MapToMap(paramsRv.Index(i).Interface(), item, mapping...); err != nil {
if err = MapToMap(paramsRv.Index(i).Interface(), item, paramKeyToAttrMap...); err != nil {
return err
}
pointerSlice.Index(i).Set(item.Elem())

View File

@ -23,7 +23,7 @@ import (
// It calls function `doMapToMaps` internally if `pointer` is type of *[]map/*[]*map for converting.
// It calls function `doStruct` internally if `pointer` is type of *struct/**struct for converting.
// It calls function `doStructs` internally if `pointer` is type of *[]struct/*[]*struct for converting.
func Scan(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) {
func Scan(params interface{}, pointer interface{}, paramKeyToAttrMap ...map[string]string) (err error) {
var (
pointerType reflect.Type
pointerKind reflect.Kind
@ -82,12 +82,12 @@ func Scan(params interface{}, pointer interface{}, mapping ...map[string]string)
pointerElemKind = pointerElem.Kind()
keyToAttributeNameMapping map[string]string
)
if len(mapping) > 0 {
keyToAttributeNameMapping = mapping[0]
if len(paramKeyToAttrMap) > 0 {
keyToAttributeNameMapping = paramKeyToAttrMap[0]
}
switch pointerElemKind {
case reflect.Map:
return doMapToMap(params, pointer, mapping...)
return doMapToMap(params, pointer, paramKeyToAttrMap...)
case reflect.Array, reflect.Slice:
var (
@ -99,7 +99,7 @@ func Scan(params interface{}, pointer interface{}, mapping ...map[string]string)
sliceElemKind = sliceElem.Kind()
}
if sliceElemKind == reflect.Map {
return doMapToMaps(params, pointer, mapping...)
return doMapToMaps(params, pointer, paramKeyToAttrMap...)
}
return doStructs(params, pointer, keyToAttributeNameMapping, "")

View File

@ -31,8 +31,8 @@ import (
// It will automatically convert the first letter of the key to uppercase
// in mapping procedure to do the matching.
// It ignores the map key, if it does not match.
func Struct(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) {
return Scan(params, pointer, mapping...)
func Struct(params interface{}, pointer interface{}, paramKeyToAttrMap ...map[string]string) (err error) {
return Scan(params, pointer, paramKeyToAttrMap...)
}
// StructTag acts as Struct but also with support for priority tag feature, which retrieves the
@ -85,7 +85,7 @@ func doStructWithJsonCheck(params interface{}, pointer interface{}) (err error,
}
// doStruct is the core internal converting function for any data to struct.
func doStruct(params interface{}, pointer interface{}, mapping map[string]string, priorityTag string) (err error) {
func doStruct(params interface{}, pointer interface{}, paramKeyToAttrMap map[string]string, priorityTag string) (err error) {
if params == nil {
// If `params` is nil, no conversion.
return nil
@ -252,7 +252,7 @@ func doStruct(params interface{}, pointer interface{}, mapping map[string]string
continue
}
}
if err = doStruct(paramsMap, elemFieldValue, mapping, priorityTag); err != nil {
if err = doStruct(paramsMap, elemFieldValue, paramKeyToAttrMap, priorityTag); err != nil {
return err
}
} else {
@ -264,7 +264,7 @@ func doStruct(params interface{}, pointer interface{}, mapping map[string]string
return nil
}
// The key of the tagMap is the attribute name of the struct,
// 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)
@ -292,11 +292,27 @@ func doStruct(params interface{}, pointer interface{}, mapping map[string]string
paramsMap[attributeName] = paramsMap[tagName]
}
}
// To convert value base on custom parameter key to attribute name map.
err = doStructBaseOnParamKeyToAttrMap(
pointerElemReflectValue,
paramsMap,
paramKeyToAttrMap,
doneMap,
)
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 precise attribute name.
err = doStructBaseOnAttribute(
pointerElemReflectValue,
paramsMap,
mapping,
paramKeyToAttrMap,
doneMap,
attrToCheckNameMap,
)
@ -307,11 +323,12 @@ func doStruct(params interface{}, pointer interface{}, mapping map[string]string
if len(doneMap) == len(attrToCheckNameMap) {
return nil
}
// To convert value base on parameter map.
err = doStructBaseOnParamMap(
pointerElemReflectValue,
paramsMap,
mapping,
paramKeyToAttrMap,
doneMap,
attrToCheckNameMap,
attrToTagCheckNameMap,
@ -323,17 +340,47 @@ func doStruct(params interface{}, pointer interface{}, mapping map[string]string
return nil
}
func doStructBaseOnParamKeyToAttrMap(
pointerElemReflectValue reflect.Value,
paramsMap map[string]interface{},
paramKeyToAttrMap map[string]string,
doneAttrMap map[string]struct{},
) error {
if len(paramKeyToAttrMap) == 0 {
return nil
}
for paramKey, attrName := range paramKeyToAttrMap {
paramValue, ok := paramsMap[paramKey]
if !ok {
continue
}
// If the attribute name is already checked converting, then skip it.
if _, ok = doneAttrMap[attrName]; ok {
continue
}
// Mark it done.
doneAttrMap[attrName] = struct{}{}
if err := bindVarToStructAttr(
pointerElemReflectValue, attrName, paramValue, paramKeyToAttrMap,
); err != nil {
return err
}
}
return nil
}
func doStructBaseOnAttribute(
pointerElemReflectValue reflect.Value,
paramsMap map[string]interface{},
mapping map[string]string,
doneMap map[string]struct{},
paramKeyToAttrMap map[string]string,
doneAttrMap map[string]struct{},
attrToCheckNameMap map[string]string,
) error {
var customMappingAttrMap = make(map[string]struct{})
if len(mapping) > 0 {
if len(paramKeyToAttrMap) > 0 {
// It ignores the attribute names if it is specified in the `paramKeyToAttrMap`.
for paramName := range paramsMap {
if passedAttrKey, ok := mapping[paramName]; ok {
if passedAttrKey, ok := paramKeyToAttrMap[paramName]; ok {
customMappingAttrMap[passedAttrKey] = struct{}{}
}
}
@ -344,17 +391,19 @@ func doStructBaseOnAttribute(
if !ok {
continue
}
// If the attribute name is in custom mapping, it then ignores this converting.
// If the attribute name is in custom paramKeyToAttrMap, it then ignores this converting.
if _, ok = customMappingAttrMap[attrName]; ok {
continue
}
// If the attribute name is already checked converting, then skip it.
if _, ok = doneMap[attrName]; ok {
if _, ok = doneAttrMap[attrName]; ok {
continue
}
// Mark it done.
doneMap[attrName] = struct{}{}
if err := bindVarToStructAttr(pointerElemReflectValue, attrName, paramValue, mapping); err != nil {
doneAttrMap[attrName] = struct{}{}
if err := bindVarToStructAttr(
pointerElemReflectValue, attrName, paramValue, paramKeyToAttrMap,
); err != nil {
return err
}
}
@ -364,8 +413,8 @@ func doStructBaseOnAttribute(
func doStructBaseOnParamMap(
pointerElemReflectValue reflect.Value,
paramsMap map[string]interface{},
mapping map[string]string,
doneMap map[string]struct{},
paramKeyToAttrMap map[string]string,
doneAttrMap map[string]struct{},
attrToCheckNameMap map[string]string,
attrToTagCheckNameMap map[string]string,
tagToAttrNameMap map[string]string,
@ -375,45 +424,35 @@ func doStructBaseOnParamMap(
checkName string
)
for paramName, paramValue := range paramsMap {
attrName = ""
// It firstly checks the passed mapping rules.
if len(mapping) > 0 {
if passedAttrKey, ok := mapping[paramName]; ok {
attrName = passedAttrKey
}
}
// It secondly checks the predefined tags and matching rules.
// It firstly considers `paramName` as accurate tag name,
// and retrieve attribute name from `tagToAttrNameMap` .
attrName = tagToAttrNameMap[paramName]
if attrName == "" {
// 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 '-'/'_'/'.'/' '.
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
}
// 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
}
}
}
// 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
}
// 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
}
}
}
@ -423,12 +462,14 @@ func doStructBaseOnParamMap(
continue
}
// If the attribute name is already checked converting, then skip it.
if _, ok := doneMap[attrName]; ok {
if _, ok := doneAttrMap[attrName]; ok {
continue
}
// Mark it done.
doneMap[attrName] = struct{}{}
if err := bindVarToStructAttr(pointerElemReflectValue, attrName, paramValue, mapping); err != nil {
doneAttrMap[attrName] = struct{}{}
if err := bindVarToStructAttr(
pointerElemReflectValue, attrName, paramValue, paramKeyToAttrMap,
); err != nil {
return err
}
}
@ -438,7 +479,7 @@ func doStructBaseOnParamMap(
// bindVarToStructAttr sets value to struct object attribute by name.
func bindVarToStructAttr(
structReflectValue reflect.Value,
attrName string, value interface{}, mapping map[string]string,
attrName string, value interface{}, paramKeyToAttrMap map[string]string,
) (err error) {
structFieldValue := structReflectValue.FieldByName(attrName)
if !structFieldValue.IsValid() {
@ -450,7 +491,7 @@ func bindVarToStructAttr(
}
defer func() {
if exception := recover(); exception != nil {
if err = bindVarToReflectValue(structFieldValue, value, mapping); err != nil {
if err = bindVarToReflectValue(structFieldValue, value, paramKeyToAttrMap); err != nil {
err = gerror.Wrapf(err, `error binding value to attribute "%s"`, attrName)
}
}
@ -575,7 +616,9 @@ func bindVarToReflectValueWithInterfaceCheck(reflectValue reflect.Value, value i
}
// bindVarToReflectValue sets `value` to reflect value object `structFieldValue`.
func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}, mapping map[string]string) (err error) {
func bindVarToReflectValue(
structFieldValue reflect.Value, value interface{}, paramKeyToAttrMap map[string]string,
) (err error) {
// JSON content converting.
err, ok := doStructWithJsonCheck(value, structFieldValue)
if err != nil {
@ -600,7 +643,7 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}, ma
// Converting using reflection by kind.
switch kind {
case reflect.Map:
return doMapToMap(value, structFieldValue, mapping)
return doMapToMap(value, structFieldValue, paramKeyToAttrMap)
case reflect.Struct:
// Recursively converting for struct attribute.
@ -716,12 +759,12 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}, ma
return err
}
elem := item.Elem()
if err = bindVarToReflectValue(elem, value, mapping); err == nil {
if err = bindVarToReflectValue(elem, value, paramKeyToAttrMap); err == nil {
structFieldValue.Set(elem.Addr())
}
} else {
// Not empty pointer, it assigns values to it.
return bindVarToReflectValue(structFieldValue.Elem(), value, mapping)
return bindVarToReflectValue(structFieldValue.Elem(), value, paramKeyToAttrMap)
}
// It mainly and specially handles the interface of nil value.

View File

@ -16,8 +16,8 @@ import (
// Structs converts any slice to given struct slice.
// Also see Scan, Struct.
func Structs(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) {
return Scan(params, pointer, mapping...)
func Structs(params interface{}, pointer interface{}, paramKeyToAttrMap ...map[string]string) (err error) {
return Scan(params, pointer, paramKeyToAttrMap...)
}
// StructsTag acts as Structs but also with support for priority tag feature, which retrieves the
@ -34,7 +34,9 @@ func StructsTag(params interface{}, pointer interface{}, priorityTag string) (er
// The parameter `pointer` should be type of pointer to slice of struct.
// Note that if `pointer` is a pointer to another pointer of type of slice of struct,
// it will create the struct/pointer internally.
func doStructs(params interface{}, pointer interface{}, mapping map[string]string, priorityTag string) (err error) {
func doStructs(
params interface{}, pointer interface{}, paramKeyToAttrMap map[string]string, priorityTag string,
) (err error) {
if params == nil {
// If `params` is nil, no conversion.
return nil
@ -133,7 +135,7 @@ func doStructs(params interface{}, pointer interface{}, mapping map[string]strin
if !tempReflectValue.IsValid() {
tempReflectValue = reflect.New(itemType.Elem()).Elem()
}
if err = doStruct(paramsList[i], tempReflectValue, mapping, priorityTag); err != nil {
if err = doStruct(paramsList[i], tempReflectValue, paramKeyToAttrMap, priorityTag); err != nil {
return err
}
reflectElemArray.Index(i).Set(tempReflectValue.Addr())
@ -147,7 +149,7 @@ func doStructs(params interface{}, pointer interface{}, mapping map[string]strin
} else {
tempReflectValue = reflect.New(itemType).Elem()
}
if err = doStruct(paramsList[i], tempReflectValue, mapping, priorityTag); err != nil {
if err = doStruct(paramsList[i], tempReflectValue, paramKeyToAttrMap, priorityTag); err != nil {
return err
}
reflectElemArray.Index(i).Set(tempReflectValue)

View File

@ -17,6 +17,33 @@ import (
"github.com/gogf/gf/v2/util/gconv"
)
func Test_Scan_WithMapParameter(t *testing.T) {
type User struct {
Uid int
Name string
}
gtest.C(t, func(t *gtest.T) {
for i := 0; i < 100; i++ {
var (
user = new(User)
params = g.Map{
"uid": 1,
"myname": "john",
"name": "smith",
}
)
err := gconv.Scan(params, user, g.MapStrStr{
"myname": "Name",
})
t.AssertNil(err)
t.Assert(user, &User{
Uid: 1,
Name: "john",
})
}
})
}
func Test_Scan_StructStructs(t *testing.T) {
type User struct {
Uid int