Cobra: ExactArgs + ValidArgs + OnlyValidArgs

Created on 14 Sep 2018  路  8Comments  路  Source: spf13/cobra

I have a command called i3 that must have at exactly one argument given to it, and the argument can either be "load" or "disable". By passing the command "ExactArgs(1)" I can make it do exactly one arg, but it doesn't respect "ValidArgs". I can pass "OnlyValidArgs" to make it respect "ValidArgs", but I can't pass it both. Something like Args: cobra.ExactArgs(1) && cobra.OnlyValidArgs(cobra, args) would be awesome.

var i3Cmd = &cobra.Command{
    Use:       "i3",
    Short:     "A brief description of your command",
    Long:      ``,
    ValidArgs: []string{"load", "disable"},
    Args:      cobra.ExactArgs(1),
        Run: func(cmd *cobra.Command, args []string) {
        fmt.Println("i3 called")
    },
}

PASS go run main.go i3 --> Error: accepts 1 arg(s), received 0
PASS go run main.go i3 hello world --> Error: accepts 1 arg(s), received 2
FAIL go run main.go i3 chicken --> i3 called
PASS go run main.go i3 load --> i3 called

ValidArgs is being ignored because cobra.OnlyValidArgs(cmd, args) isn't called. When i pass in chicken, I want it to error because 'load' is the only valid arg.

Trying again with a custom Arg filter.

var i3Cmd = &cobra.Command{
    Use:       "i3",
    Short:     "A brief description of your command",
    Long:      ``,
    ValidArgs: []string{"load", "disable"},
    Args: func(cmd *cobra.Command, args []string) error {
        if len(args) != 1 {
            return errors.New("Requires exactly 1 arg.")
        }
        return cobra.OnlyValidArgs(cmd, args)
    },
    Run: func(cmd *cobra.Command, args []string) {
        fmt.Println("i3 called")
    },
}

PASS go run main.go i3 hello --> Error: invalid argument "hello" for "dot i3"
PASS go run main.go i3 load --> i3 called
PASS go run main.go i3 hello world --> Error: Requires exactly 1 arg
PASS go run main.go i3 --> Error: Requires exactly 1 arg.

The example above works... but it doesn't use cobra.ExactArgs(1). I don't like re-inventing the wheel. How can I use both OnlyValidArgs and ExactArgs together? I tried:

var i3Cmd = &cobra.Command{
    Use:       "i3",
    Short:     "A brief description of your command",
    Long:      ``,
    ValidArgs: []string{"load"},
    Args: func(cmd *cobra.Command, args []string) error {
        err := cobra.ExactArgs(1)
        if err != nil {
            return err // <--- this is a compile error
        }   
        return cobra.OnlyValidArgs(cmd, args)
    },
    Run: func(cmd *cobra.Command, args []string) {
        fmt.Println("i3 called")
    },
}

But ExactArgs returns PositionalArgs.. which looks to be a Type.. I'm not very familiar with Go, so this is confusing to me. I don't know how to combine the two. Any insight would be appreciated!

Also.. If I set ValidArgs and OnlyValidArgs, and an invalid command arg is provided, the user isn't notified of what the valid Args are. Is this the same feature request as #571? That seems like it would be very helpful.

related #284
related #571
related #346

kinstale

Most helpful comment

@patrick-motard for your exact use-case you can use cobra.ExactValidArgs(1) as a value for ValidArgs.

More general solution would be something like this:

var cmd = &cobra.Command{
    Use:       "cmd",
    Short:     "General solution",
    ValidArgs: []string{"some", "acceptable", "values"},
    Args:      matchAll(cobra.MinimumNArgs(1), cobra.OnlyValidArgs),
    Run: func(cmd *cobra.Command, args []string) {
        fmt.Println("cmd called")
    },
}

func matchAll(checks ...cobra.PositionalArgs) cobra.PositionalArgs {
    return func(cmd *cobra.Command, args []string) error {
        for _, check := range checks {
            if err := check(cmd, args); err != nil {
                return err
            }
        }
        return nil
    }
}

Here the matchAll function will effectively combine any number of cobra.PositionalArgs you pass to it, including the ones from cobra package and arbitrary ones you might have defined in your code.

If you think it would be a good idea to add this feature to the library itself I can submit a pull request.

All 8 comments

I would like to use OnlyValidArgs and MinimumNArgs(1)

@patrick-motard for your exact use-case you can use cobra.ExactValidArgs(1) as a value for ValidArgs.

More general solution would be something like this:

var cmd = &cobra.Command{
    Use:       "cmd",
    Short:     "General solution",
    ValidArgs: []string{"some", "acceptable", "values"},
    Args:      matchAll(cobra.MinimumNArgs(1), cobra.OnlyValidArgs),
    Run: func(cmd *cobra.Command, args []string) {
        fmt.Println("cmd called")
    },
}

func matchAll(checks ...cobra.PositionalArgs) cobra.PositionalArgs {
    return func(cmd *cobra.Command, args []string) error {
        for _, check := range checks {
            if err := check(cmd, args); err != nil {
                return err
            }
        }
        return nil
    }
}

Here the matchAll function will effectively combine any number of cobra.PositionalArgs you pass to it, including the ones from cobra package and arbitrary ones you might have defined in your code.

If you think it would be a good idea to add this feature to the library itself I can submit a pull request.

This issue is being marked as stale due to a long period of inactivity

I would use the feature proposed by @Antolius

@firelizzard18 you might want to try #841.

@umarcor I want a function that composes multiple argument validators. Your MR does not provide this. Here is what I am using:

func args(fns ...cobra.PositionalArgs) cobra.PositionalArgs {
    return func(cmd *cobra.Command, args []string) error {
        for _, fn := range fns {
            if err := fn(cmd, args); err != nil {
                return err
            }
        }
        return nil
    }
}

@firelizzard18, my bad. The reference should have been #896.

@umarcor I +1'ed #896. Hopefully that will go somewhere...

Was this page helpful?
0 / 5 - 0 ratings

Related issues

supershal picture supershal  路  4Comments

icholy picture icholy  路  6Comments

mdaymard picture mdaymard  路  5Comments

anuraaga picture anuraaga  路  5Comments

groenborg picture groenborg  路  5Comments