Skip to content

Commit

Permalink
Merge pull request #893 from rliebz/fs-panic
Browse files Browse the repository at this point in the history
Avoid panic for missing flag value
  • Loading branch information
asahasrabuddhe committed Sep 14, 2019
2 parents c71fbce + 06e3d38 commit c0c49a1
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 34 deletions.
8 changes: 4 additions & 4 deletions app.go
Expand Up @@ -199,12 +199,12 @@ func (a *App) Run(arguments []string) (err error) {
// always appends the completion flag at the end of the command
shellComplete, arguments := checkShellCompleteFlag(a, arguments)

_, err = a.newFlagSet()
set, err := a.newFlagSet()
if err != nil {
return err
}

set, err := parseIter(a, arguments[1:])
err = parseIter(set, a, arguments[1:])
nerr := normalizeFlags(a.Flags, set)
context := NewContext(a, set, nil)
if nerr != nil {
Expand Down Expand Up @@ -322,12 +322,12 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
}
a.Commands = newCmds

_, err = a.newFlagSet()
set, err := a.newFlagSet()
if err != nil {
return err
}

set, err := parseIter(a, ctx.Args().Tail())
err = parseIter(set, a, ctx.Args().Tail())
nerr := normalizeFlags(a.Flags, set)
context := NewContext(a, set, ctx)

Expand Down
45 changes: 45 additions & 0 deletions app_test.go
Expand Up @@ -659,6 +659,17 @@ func TestApp_UseShortOptionHandling(t *testing.T) {
expect(t, name, expected)
}

func TestApp_UseShortOptionHandling_missing_value(t *testing.T) {
app := NewApp()
app.UseShortOptionHandling = true
app.Flags = []Flag{
StringFlag{Name: "name, n"},
}

err := app.Run([]string{"", "-n"})
expect(t, err, errors.New("flag needs an argument: -n"))
}

func TestApp_UseShortOptionHandlingCommand(t *testing.T) {
var one, two bool
var name string
Expand Down Expand Up @@ -688,6 +699,21 @@ func TestApp_UseShortOptionHandlingCommand(t *testing.T) {
expect(t, name, expected)
}

func TestApp_UseShortOptionHandlingCommand_missing_value(t *testing.T) {
app := NewApp()
app.UseShortOptionHandling = true
command := Command{
Name: "cmd",
Flags: []Flag{
StringFlag{Name: "name, n"},
},
}
app.Commands = []Command{command}

err := app.Run([]string{"", "cmd", "-n"})
expect(t, err, errors.New("flag needs an argument: -n"))
}

func TestApp_UseShortOptionHandlingSubCommand(t *testing.T) {
var one, two bool
var name string
Expand Down Expand Up @@ -722,6 +748,25 @@ func TestApp_UseShortOptionHandlingSubCommand(t *testing.T) {
expect(t, name, expected)
}

func TestApp_UseShortOptionHandlingSubCommand_missing_value(t *testing.T) {
app := NewApp()
app.UseShortOptionHandling = true
command := Command{
Name: "cmd",
}
subCommand := Command{
Name: "sub",
Flags: []Flag{
StringFlag{Name: "name, n"},
},
}
command.Subcommands = []Command{subCommand}
app.Commands = []Command{command}

err := app.Run([]string{"", "cmd", "sub", "-n"})
expect(t, err, errors.New("flag needs an argument: -n"))
}

func TestApp_Float64Flag(t *testing.T) {
var meters float64

Expand Down
7 changes: 6 additions & 1 deletion command.go
Expand Up @@ -193,7 +193,12 @@ func (c *Command) parseFlags(args Args) (*flag.FlagSet, error) {
args = reorderArgs(args)
}

set, err := parseIter(c, args)
set, err := c.newFlagSet()
if err != nil {
return nil, err
}

err = parseIter(set, c, args)
if err != nil {
return nil, err
}
Expand Down
43 changes: 24 additions & 19 deletions command_test.go
Expand Up @@ -70,29 +70,34 @@ func TestParseAndRunShortOpts(t *testing.T) {
{[]string{"foo", "test", "-af"}, nil, []string{}},
{[]string{"foo", "test", "-cf"}, nil, []string{}},
{[]string{"foo", "test", "-acf"}, nil, []string{}},
{[]string{"foo", "test", "-invalid"}, errors.New("flag provided but not defined: -invalid"), []string{}},
{[]string{"foo", "test", "-invalid"}, errors.New("flag provided but not defined: -invalid"), nil},
{[]string{"foo", "test", "-acf", "arg1", "-invalid"}, nil, []string{"arg1", "-invalid"}},
}

var args []string
cmd := Command{
Name: "test",
Usage: "this is for testing",
Description: "testing",
Action: func(c *Context) error {
args = c.Args()
return nil
},
SkipArgReorder: true,
UseShortOptionHandling: true,
Flags: []Flag{
BoolFlag{Name: "abc, a"},
BoolFlag{Name: "cde, c"},
BoolFlag{Name: "fgh, f"},
},
{[]string{"foo", "test", "-acfi", "not-arg", "arg1", "-invalid"}, nil, []string{"arg1", "-invalid"}},
{[]string{"foo", "test", "-i", "ivalue"}, nil, []string{}},
{[]string{"foo", "test", "-i", "ivalue", "arg1"}, nil, []string{"arg1"}},
{[]string{"foo", "test", "-i"}, errors.New("flag needs an argument: -i"), nil},
}

for _, c := range cases {
var args []string
cmd := Command{
Name: "test",
Usage: "this is for testing",
Description: "testing",
Action: func(c *Context) error {
args = c.Args()
return nil
},
SkipArgReorder: true,
UseShortOptionHandling: true,
Flags: []Flag{
BoolFlag{Name: "abc, a"},
BoolFlag{Name: "cde, c"},
BoolFlag{Name: "fgh, f"},
StringFlag{Name: "ijk, i"},
},
}

app := NewApp()
app.Commands = []Command{cmd}

Expand Down
22 changes: 12 additions & 10 deletions parse.go
Expand Up @@ -14,22 +14,17 @@ type iterativeParser interface {
// iteratively catch parsing errors. This way we achieve LR parsing without
// transforming any arguments. Otherwise, there is no way we can discriminate
// combined short options from common arguments that should be left untouched.
func parseIter(ip iterativeParser, args []string) (*flag.FlagSet, error) {
func parseIter(set *flag.FlagSet, ip iterativeParser, args []string) error {
for {
set, err := ip.newFlagSet()
if err != nil {
return nil, err
}

err = set.Parse(args)
err := set.Parse(args)
if !ip.useShortOptionHandling() || err == nil {
return set, err
return err
}

errStr := err.Error()
trimmed := strings.TrimPrefix(errStr, "flag provided but not defined: ")
if errStr == trimmed {
return nil, err
return err
}

// regenerate the initial args with the split short opts
Expand All @@ -42,14 +37,21 @@ func parseIter(ip iterativeParser, args []string) (*flag.FlagSet, error) {

shortOpts := splitShortOptions(set, trimmed)
if len(shortOpts) == 1 {
return nil, err
return err
}

// add each short option and all remaining arguments
newArgs = append(newArgs, shortOpts...)
newArgs = append(newArgs, args[i+1:]...)
args = newArgs
}

// Since custom parsing failed, replace the flag set before retrying
newSet, err := ip.newFlagSet()
if err != nil {
return err
}
*set = *newSet
}
}

Expand Down

0 comments on commit c0c49a1

Please sign in to comment.