Skip to content

Commit

Permalink
Merge pull request #393 from woky/master
Browse files Browse the repository at this point in the history
Support per-command PassAfterNonOption
  • Loading branch information
jessevdk authored Jun 15, 2024
2 parents f0b7af3 + 6c1df9d commit edab227
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 1 deletion.
11 changes: 11 additions & 0 deletions command.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ type Command struct {
// Whether positional arguments are required
ArgsRequired bool

// Whether to pass all arguments after the first non option as remaining
// command line arguments. This is equivalent to strict POSIX processing.
// This is command-local version of PassAfterNonOption Parser flag. It
// cannot be turned off when PassAfterNonOption Parser flag is set.
PassAfterNonOption bool

commands []*Command
hasBuiltinHelpGroup bool
args []*Arg
Expand Down Expand Up @@ -244,6 +250,7 @@ func (c *Command) scanSubcommandHandler(parentg *Group) scanHandler {
longDescription := mtag.Get("long-description")
subcommandsOptional := mtag.Get("subcommands-optional")
aliases := mtag.GetMany("alias")
passAfterNonOption := mtag.Get("pass-after-non-option")

subc, err := c.AddCommand(subcommand, shortDescription, longDescription, ptrval.Interface())

Expand All @@ -261,6 +268,10 @@ func (c *Command) scanSubcommandHandler(parentg *Group) scanHandler {
subc.Aliases = aliases
}

if len(passAfterNonOption) > 0 {
subc.PassAfterNonOption = true
}

return true, nil
}

Expand Down
115 changes: 115 additions & 0 deletions command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -648,3 +648,118 @@ func TestCommandPassAfterNonOptionWithPositional(t *testing.T) {
assertStringArray(t, opts.Bar.args, []string{})
assertStringArray(t, opts.Bar.Positional.Args, []string{"baz", "-v", "-g"})
}

type cmdLocalPassAfterNonOptionMix struct {
FlagA bool `short:"a"`
Cmd1 struct {
FlagB bool `short:"b"`
Positional struct {
Args []string
} `positional-args:"yes"`
} `command:"cmd1" pass-after-non-option:"yes"`
Cmd2 struct {
FlagB bool `short:"b"`
Positional struct {
Args []string
} `positional-args:"yes"`
} `command:"cmd2"`
}

func TestCommandLocalPassAfterNonOptionMixCmd1(t *testing.T) {
var opts cmdLocalPassAfterNonOptionMix

assertParseSuccess(t, &opts, "cmd1", "-b", "arg1", "-a", "arg2", "-x")

if opts.FlagA {
t.Errorf("Expected FlagA to be false")
}

if !opts.Cmd1.FlagB {
t.Errorf("Expected Cmd1.FlagB to be true")
}

assertStringArray(t, opts.Cmd1.Positional.Args, []string{"arg1", "-a", "arg2", "-x"})
}

func TestCommandLocalPassAfterNonOptionMixCmd2(t *testing.T) {
var opts cmdLocalPassAfterNonOptionMix

assertParseSuccess(t, &opts, "cmd2", "-b", "arg1", "-a", "arg2")

if !opts.FlagA {
t.Errorf("Expected FlagA to be true")
}

if !opts.Cmd2.FlagB {
t.Errorf("Expected Cmd2.FlagB to be true")
}

assertStringArray(t, opts.Cmd2.Positional.Args, []string{"arg1", "arg2"})
}

func TestCommandLocalPassAfterNonOptionMixCmd2UnkownFlag(t *testing.T) {
var opts cmdLocalPassAfterNonOptionMix

assertParseFail(t, ErrUnknownFlag, "unknown flag `x'", &opts, "cmd2", "-b", "arg1", "-a", "arg2", "-x")
}

type cmdLocalPassAfterNonOptionNest struct {
FlagA bool `short:"a"`
Cmd1 struct {
FlagB bool `short:"b"`
Cmd2 struct {
FlagC bool `short:"c"`
Cmd3 struct {
FlagD bool `short:"d"`
} `command:"cmd3"`
} `command:"cmd2" subcommands-optional:"yes" pass-after-non-option:"yes"`
} `command:"cmd1"`
}

func TestCommandLocalPassAfterNonOptionNest1(t *testing.T) {
var opts cmdLocalPassAfterNonOptionNest

ret := assertParseSuccess(t, &opts, "cmd1", "cmd2", "-a", "x", "-b", "cmd3", "-c", "-d")

if !opts.FlagA {
t.Errorf("Expected FlagA to be true")
}

if opts.Cmd1.FlagB {
t.Errorf("Expected Cmd1.FlagB to be false")
}

if opts.Cmd1.Cmd2.FlagC {
t.Errorf("Expected Cmd1.Cmd2.FlagC to be false")
}

if opts.Cmd1.Cmd2.Cmd3.FlagD {
t.Errorf("Expected Cmd1.Cmd2.Cmd3.FlagD to be false")
}

assertStringArray(t, ret, []string{"x", "-b", "cmd3", "-c", "-d"})
}

func TestCommandLocalPassAfterNonOptionNest2(t *testing.T) {
var opts cmdLocalPassAfterNonOptionNest

ret := assertParseSuccess(t, &opts, "cmd1", "cmd2", "cmd3", "-a", "x", "-b", "-c", "-d")

if !opts.FlagA {
t.Errorf("Expected FlagA to be true")
}

if !opts.Cmd1.FlagB {
t.Errorf("Expected Cmd1.FlagB to be true")
}

if !opts.Cmd1.Cmd2.FlagC {
t.Errorf("Expected Cmd1.Cmd2.FlagC to be true")
}

if !opts.Cmd1.Cmd2.Cmd3.FlagD {
t.Errorf("Expected Cmd1.Cmd2.Cmd3.FlagD to be true")
}

assertStringArray(t, ret, []string{"x"})
}
2 changes: 1 addition & 1 deletion parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ func (p *Parser) ParseArgs(args []string) ([]string, error) {
}

if !argumentIsOption(arg) {
if (p.Options&PassAfterNonOption) != None && s.lookup.commands[arg] == nil {
if ((p.Options&PassAfterNonOption) != None || s.command.PassAfterNonOption) && s.lookup.commands[arg] == nil {
// If PassAfterNonOption is set then all remaining arguments
// are considered positional
if err = s.addArgs(s.arg); err != nil {
Expand Down

0 comments on commit edab227

Please sign in to comment.