From 5e297a179a58c813047779658dabb8435ad18aa5 Mon Sep 17 00:00:00 2001 From: sttk Date: Tue, 15 Oct 2024 23:15:12 +0900 Subject: [PATCH] fix: modified to inherit end opt flag after parsing until sub command --- cmd.go | 13 +++++-- cmd_test.go | 6 ++-- parse-with.go | 21 ++++++------ parse-with_test.go | 84 ++++++++++++++++++++++++++++++++++++++++++++++ parse.go | 23 +++++++------ parse_test.go | 64 +++++++++++++++++++++++++++++++---- 6 files changed, 178 insertions(+), 33 deletions(-) diff --git a/cmd.go b/cmd.go index 1e7434a..4504bf9 100644 --- a/cmd.go +++ b/cmd.go @@ -19,7 +19,8 @@ type Cmd struct { Args []string OptCfgs []OptCfg - opts map[string][]string + opts map[string][]string + isAfterEndOpt bool _args []string } @@ -40,7 +41,7 @@ func NewCmd() Cmd { return Cmd{Name: name, Args: []string{}, opts: make(map[string][]string), _args: args} } -func (cmd Cmd) subCmd(fromIndex int) Cmd { +func (cmd Cmd) subCmd(fromIndex int, isAfterEndOpt bool) Cmd { var name string if len(cmd._args) > fromIndex { name = cmd._args[fromIndex] @@ -51,7 +52,13 @@ func (cmd Cmd) subCmd(fromIndex int) Cmd { args = cmd._args[fromIndex+1:] } - return Cmd{Name: name, Args: []string{}, opts: make(map[string][]string), _args: args} + return Cmd{ + Name: name, + Args: []string{}, + opts: make(map[string][]string), + isAfterEndOpt: isAfterEndOpt, + _args: args, + } } // HasOpt is the method that checks whether an option with the specified name exists. diff --git a/cmd_test.go b/cmd_test.go index 1426990..aece747 100644 --- a/cmd_test.go +++ b/cmd_test.go @@ -69,7 +69,7 @@ func TestCmd_subCmd(t *testing.T) { cmd := NewCmd() cmd._args = []string{"--foo", "-b", "qux", "--corge"} - subCmd := cmd.subCmd(2) + subCmd := cmd.subCmd(2, false) assert.Equal(t, subCmd.Name, "qux") assert.Equal(t, subCmd._args, []string{"--corge"}) } @@ -78,7 +78,7 @@ func TestCmd_subCmd_withNoArg(t *testing.T) { cmd := NewCmd() cmd._args = []string{"--foo", "-b", "qux"} - subCmd := cmd.subCmd(2) + subCmd := cmd.subCmd(2, false) assert.Equal(t, subCmd.Name, "qux") assert.Equal(t, subCmd._args, []string(nil)) } @@ -87,7 +87,7 @@ func TestCmd_subCmd_empty(t *testing.T) { cmd := NewCmd() cmd._args = []string{"--foo", "-b"} - subCmd := cmd.subCmd(2) + subCmd := cmd.subCmd(2, false) assert.Equal(t, subCmd.Name, "") assert.Equal(t, subCmd._args, []string(nil)) } diff --git a/parse-with.go b/parse-with.go index 8ce5aa5..d39b34e 100644 --- a/parse-with.go +++ b/parse-with.go @@ -38,7 +38,7 @@ const anyOption = "*" // The option configurations used to parsing are set into this Cmd instance, and it can be // retrieved from its field: Cmd#OptCfgs. func (cmd *Cmd) ParseWith(optCfgs []OptCfg) error { - _, err := cmd.parseArgsWith(optCfgs, false) + _, _, err := cmd.parseArgsWith(optCfgs, false) cmd.OptCfgs = optCfgs return err } @@ -55,18 +55,18 @@ func (cmd *Cmd) ParseWith(optCfgs []OptCfg) error { // The option configurations used to parsing are set into this Cmd instance, and it can be // retrieved from its field: Cmd#OptCfgs. func (cmd *Cmd) ParseUntilSubCmdWith(optCfgs []OptCfg) (Cmd, error) { - idx, err := cmd.parseArgsWith(optCfgs, true) + idx, isAfterEndOpt, err := cmd.parseArgsWith(optCfgs, true) cmd.OptCfgs = optCfgs if idx < 0 { return Cmd{}, err } - return cmd.subCmd(idx), err + return cmd.subCmd(idx, isAfterEndOpt), err } func (cmd *Cmd) parseArgsWith( optCfgs []OptCfg, untilFirstArg bool, -) (int, error) { +) (int, bool, error) { const ANY_OPT = "*" hasAnyOpt := false @@ -109,18 +109,18 @@ func (cmd *Cmd) parseArgsWith( _, exists := optMap[storeKey] if exists { e := errors.StoreKeyIsDuplicated{StoreKey: storeKey, Name: firstName} - return -1, e + return -1, cmd.isAfterEndOpt, e } optMap[storeKey] = EMPTY_STRUCT if !cfg.HasArg { if cfg.IsArray { e := errors.ConfigIsArrayButHasNoArg{StoreKey: storeKey, Name: firstName} - return -1, e + return -1, cmd.isAfterEndOpt, e } if cfg.Defaults != nil { e := errors.ConfigHasDefaultsButHasNoArg{StoreKey: storeKey, Name: firstName} - return -1, e + return -1, cmd.isAfterEndOpt, e } } @@ -131,7 +131,7 @@ func (cmd *Cmd) parseArgsWith( _, exists := cfgMap[nm] if exists { e := errors.OptionNameIsDuplicated{StoreKey: storeKey, Name: nm} - return -1, e + return -1, cmd.isAfterEndOpt, e } cfgMap[nm] = i } @@ -231,12 +231,13 @@ func (cmd *Cmd) parseArgsWith( } } - idx, err := parseArgs( + idx, isAfterEndOpt, err := parseArgs( cmd._args, collectArgs, collectOpts, takeOptArgs, untilFirstArg, + cmd.isAfterEndOpt, ) for _, cfg := range optCfgs { @@ -273,5 +274,5 @@ func (cmd *Cmd) parseArgsWith( } } - return idx, err + return idx, isAfterEndOpt, err } diff --git a/parse-with_test.go b/parse-with_test.go index ce2a211..74e7052 100644 --- a/parse-with_test.go +++ b/parse-with_test.go @@ -1819,3 +1819,87 @@ func TestParseUntilSubCmdWith_oneCfgUsingValidatorAndSubCmd(t *testing.T) { assert.Equal(t, subCmd.Name, "def") assert.Equal(t, subCmd.Args, []string{"ghi"}) } + +func TestParseUntilSubCmd_parseWithEndOptMark(t *testing.T) { + defer reset() + + optCfgs0 := []cliargs.OptCfg{ + cliargs.OptCfg{ + Names: []string{"foo"}, + }, + } + optCfgs1 := []cliargs.OptCfg{ + cliargs.OptCfg{ + Names: []string{"bar"}, + }, + } + + os.Args = []string{"/path/to/app", "--foo", "sub", "--", "bar", "-@"} + + cmd := cliargs.NewCmd() + subCmd, err := cmd.ParseUntilSubCmdWith(optCfgs0) + + assert.Nil(t, err) + assert.Equal(t, cmd.Name, "app") + assert.Equal(t, cmd.Args, []string{}) + assert.Equal(t, cmd.HasOpt("foo"), true) + assert.Equal(t, cmd.OptArg("foo"), "") + assert.Equal(t, cmd.OptArgs("foo"), []string(nil)) + assert.Equal(t, cmd.HasOpt("bar"), false) + assert.Equal(t, cmd.OptArg("bar"), "") + assert.Equal(t, cmd.OptArgs("bar"), []string(nil)) + + err = subCmd.ParseWith(optCfgs1) + + assert.Nil(t, err) + assert.Equal(t, subCmd.Name, "sub") + assert.Equal(t, subCmd.Args, []string{"bar", "-@"}) + assert.Equal(t, subCmd.HasOpt("foo"), false) + assert.Equal(t, subCmd.OptArg("foo"), "") + assert.Equal(t, subCmd.OptArgs("foo"), []string(nil)) + assert.Equal(t, subCmd.HasOpt("bar"), false) + assert.Equal(t, subCmd.OptArg("bar"), "") + assert.Equal(t, subCmd.OptArgs("bar"), []string(nil)) +} + +func TestParseUntilSubCmd_parseAfterEndOptMark(t *testing.T) { + defer reset() + + optCfgs0 := []cliargs.OptCfg{ + cliargs.OptCfg{ + Names: []string{"foo"}, + }, + } + optCfgs1 := []cliargs.OptCfg{ + cliargs.OptCfg{ + Names: []string{"bar"}, + }, + } + + os.Args = []string{"/path/to/app", "--", "--foo", "sub", "bar", "-@"} + + cmd := cliargs.NewCmd() + subCmd, err := cmd.ParseUntilSubCmdWith(optCfgs0) + + assert.Nil(t, err) + assert.Equal(t, cmd.Name, "app") + assert.Equal(t, cmd.Args, []string{}) + assert.Equal(t, cmd.HasOpt("foo"), false) + assert.Equal(t, cmd.OptArg("foo"), "") + assert.Equal(t, cmd.OptArgs("foo"), []string(nil)) + assert.Equal(t, cmd.HasOpt("bar"), false) + assert.Equal(t, cmd.OptArg("bar"), "") + assert.Equal(t, cmd.OptArgs("bar"), []string(nil)) + + err = subCmd.ParseWith(optCfgs1) + + assert.Nil(t, err) + assert.Equal(t, subCmd.Name, "--foo") + assert.Equal(t, subCmd.Args, []string{"sub", "bar", "-@"}) + assert.Equal(t, subCmd.HasOpt("foo"), false) + assert.Equal(t, subCmd.OptArg("foo"), "") + assert.Equal(t, subCmd.OptArgs("foo"), []string(nil)) + assert.Equal(t, subCmd.HasOpt("bar"), false) + assert.Equal(t, subCmd.OptArg("bar"), "") + assert.Equal(t, subCmd.OptArgs("bar"), []string(nil)) +} diff --git a/parse.go b/parse.go index bfa1af1..1ed66cb 100644 --- a/parse.go +++ b/parse.go @@ -60,7 +60,7 @@ func (cmd *Cmd) Parse() error { return nil } - _, err := parseArgs(cmd._args, collectArgs, collectOpts, takeOptArgs, false) + _, _, err := parseArgs(cmd._args, collectArgs, collectOpts, takeOptArgs, false, cmd.isAfterEndOpt) return err } @@ -84,11 +84,12 @@ func (cmd *Cmd) ParseUntilSubCmd() (Cmd, error) { return nil } - idx, err := parseArgs(cmd._args, collectArgs, collectOpts, takeOptArgs, true) + idx, isAfterEndOpt, err := parseArgs( + cmd._args, collectArgs, collectOpts, takeOptArgs, true, cmd.isAfterEndOpt) if idx < 0 { return Cmd{}, err } - return cmd.subCmd(idx), err + return cmd.subCmd(idx, isAfterEndOpt), err } func takeOptArgs(_opt string) bool { @@ -101,17 +102,17 @@ func parseArgs( collectOpts func(string, ...string) error, takeOptArgs func(string) bool, untilFirstArg bool, -) (int, error) { + isAfterEndOpt bool, +) (int, bool, error) { - isNonOpt := false prevOptTakingArgs := "" var firstErr error = nil L0: for iArg, arg := range osArgs { - if isNonOpt { + if isAfterEndOpt { if untilFirstArg { - return iArg, firstErr + return iArg, isAfterEndOpt, firstErr } collectArgs(arg) @@ -126,7 +127,7 @@ L0: } } else if strings.HasPrefix(arg, "--") { if len(arg) == 2 { - isNonOpt = true + isAfterEndOpt = true continue L0 } @@ -179,7 +180,7 @@ L0: } else if strings.HasPrefix(arg, "-") { if len(arg) == 1 { if untilFirstArg { - return iArg, firstErr + return iArg, isAfterEndOpt, firstErr } collectArgs(arg) continue L0 @@ -238,11 +239,11 @@ L0: } else { if untilFirstArg { - return iArg, firstErr + return iArg, isAfterEndOpt, firstErr } collectArgs(arg) } } - return -1, firstErr + return -1, isAfterEndOpt, firstErr } diff --git a/parse_test.go b/parse_test.go index 16dd1ff..af63cfe 100644 --- a/parse_test.go +++ b/parse_test.go @@ -807,10 +807,10 @@ func TestParseUntilSubCmd_subCmdIssingleHyphen(t *testing.T) { assert.Equal(t, subCmd.OptArgs("bar"), []string{"123"}) } -func TestParseUntilSubCmd_nonOpt(t *testing.T) { +func TestParseUntilSubCmd_withEndOptMark(t *testing.T) { defer reset() - os.Args = []string{"/path/to/app", "--foo", "--", "--bar", "--baz=123"} + os.Args = []string{"/path/to/app", "--foo", "sub", "--", "--bar", "--baz=123", "-@"} cmd := cliargs.NewCmd() subCmd, err := cmd.ParseUntilSubCmd() @@ -829,7 +829,7 @@ func TestParseUntilSubCmd_nonOpt(t *testing.T) { assert.Equal(t, cmd.OptArg("baz"), "") assert.Equal(t, cmd.OptArgs("baz"), []string(nil)) - assert.Equal(t, subCmd.Name, "--bar") + assert.Equal(t, subCmd.Name, "sub") assert.Equal(t, subCmd.Args, []string{}) assert.False(t, subCmd.HasOpt("foo")) @@ -845,6 +845,42 @@ func TestParseUntilSubCmd_nonOpt(t *testing.T) { err = subCmd.Parse() assert.Equal(t, err, nil) + assert.Equal(t, subCmd.Name, "sub") + assert.Equal(t, subCmd.Args, []string{"--bar", "--baz=123", "-@"}) + + assert.False(t, subCmd.HasOpt("foo")) + assert.Equal(t, subCmd.OptArg("foo"), "") + assert.Equal(t, subCmd.OptArgs("foo"), []string(nil)) + assert.False(t, subCmd.HasOpt("bar")) + assert.Equal(t, subCmd.OptArg("bar"), "") + assert.Equal(t, subCmd.OptArgs("bar"), []string(nil)) + assert.False(t, subCmd.HasOpt("baz")) + assert.Equal(t, subCmd.OptArg("baz"), "") + assert.Equal(t, subCmd.OptArgs("baz"), []string(nil)) +} + +func TestParseUntilSubCmd_afterEndOptMark(t *testing.T) { + defer reset() + + os.Args = []string{"/path/to/app", "--foo", "--", "--bar", "--baz=123", "-@"} + + cmd := cliargs.NewCmd() + subCmd, err := cmd.ParseUntilSubCmd() + + assert.Equal(t, err, nil) + assert.Equal(t, cmd.Name, "app") + assert.Equal(t, cmd.Args, []string{}) + + assert.True(t, cmd.HasOpt("foo")) + assert.Equal(t, cmd.OptArg("foo"), "") + assert.Equal(t, cmd.OptArgs("foo"), []string{}) + assert.False(t, cmd.HasOpt("bar")) + assert.Equal(t, cmd.OptArg("bar"), "") + assert.Equal(t, cmd.OptArgs("bar"), []string(nil)) + assert.False(t, cmd.HasOpt("baz")) + assert.Equal(t, cmd.OptArg("baz"), "") + assert.Equal(t, cmd.OptArgs("baz"), []string(nil)) + assert.Equal(t, subCmd.Name, "--bar") assert.Equal(t, subCmd.Args, []string{}) @@ -854,7 +890,23 @@ func TestParseUntilSubCmd_nonOpt(t *testing.T) { assert.False(t, subCmd.HasOpt("bar")) assert.Equal(t, subCmd.OptArg("bar"), "") assert.Equal(t, subCmd.OptArgs("bar"), []string(nil)) - assert.True(t, subCmd.HasOpt("baz")) - assert.Equal(t, subCmd.OptArg("baz"), "123") - assert.Equal(t, subCmd.OptArgs("baz"), []string{"123"}) + assert.False(t, subCmd.HasOpt("baz")) + assert.Equal(t, subCmd.OptArg("baz"), "") + assert.Equal(t, subCmd.OptArgs("baz"), []string(nil)) + + err = subCmd.Parse() + + assert.Equal(t, err, nil) + assert.Equal(t, subCmd.Name, "--bar") + assert.Equal(t, subCmd.Args, []string{"--baz=123", "-@"}) + + assert.False(t, subCmd.HasOpt("foo")) + assert.Equal(t, subCmd.OptArg("foo"), "") + assert.Equal(t, subCmd.OptArgs("foo"), []string(nil)) + assert.False(t, subCmd.HasOpt("bar")) + assert.Equal(t, subCmd.OptArg("bar"), "") + assert.Equal(t, subCmd.OptArgs("bar"), []string(nil)) + assert.False(t, subCmd.HasOpt("baz")) + assert.Equal(t, subCmd.OptArg("baz"), "") + assert.Equal(t, subCmd.OptArgs("baz"), []string(nil)) }