Skip to content

Commit

Permalink
fix #3417
Browse files Browse the repository at this point in the history
  • Loading branch information
gqcn committed Mar 27, 2024
1 parent 8c1db29 commit 8e59e88
Show file tree
Hide file tree
Showing 7 changed files with 211 additions and 61 deletions.
10 changes: 5 additions & 5 deletions internal/command/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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 {
Expand Down
6 changes: 3 additions & 3 deletions os/gcmd/gcmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
42 changes: 12 additions & 30 deletions os/gcmd/gcmd_command_object.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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{}{}
}
Expand Down Expand Up @@ -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()))
Expand Down Expand Up @@ -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
Expand Down
22 changes: 12 additions & 10 deletions os/gcmd/gcmd_command_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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
Expand Down
21 changes: 17 additions & 4 deletions os/gcmd/gcmd_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
161 changes: 152 additions & 9 deletions os/gcmd/gcmd_z_unit_issue_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"},
Expand Down Expand Up @@ -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}`,
)
})
}
10 changes: 10 additions & 0 deletions util/gconv/gconv_struct.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit 8e59e88

Please sign in to comment.