Cobra: Overriding "help" flag doesn't work

Created on 29 Jun 2017  路  13Comments  路  Source: spf13/cobra

Hello,

I'm trying to override the default help flag, but for some reason, it doesn't work. Here's the code:

package main

import (
    "fmt"
    "os"

    "github.com/spf13/cobra"
)

func main() {
    app := &cobra.Command{
        Use:   "testing",
        Short: "testing app",
        PersistentPreRun: func(c *cobra.Command, args []string) {
            c.Flags().BoolP("help", "h", false, "helping myself with the command "+c.Name())
        },
    }

    var name string
    hello := &cobra.Command{
        Use:   "hello",
        Short: "example hello command",
        Run: func(c *cobra.Command, args []string) {
            if name == "" {
                name = "World"
            }
            fmt.Printf("Hello, %s! \n", name)
        },
    }

    hello.Flags().StringVar(&name, "name", "", "name to greet")
    app.AddCommand(hello)

    if err := app.Execute(); err != nil {
        fmt.Println(err.Error())
        os.Exit(1)
    }
}

When ran, I see:

$  testing hello -h
example hello command

Usage:
  testing hello [flags]

Flags:
  -h, --help          help for hello
      --name string   name to greet

So the PersistentPreRun is supposed to run before each Execute() command. Then the InitHelpFlag is supposed to check if there's already a flag named help, if so, never touch it.

Am I missing something?

All 13 comments

You should declare all flags in init as it said in README.
This works for me:

package main

import (
    "fmt"
    "os"

    "github.com/spf13/cobra"
)

var app = &cobra.Command{
    Use:   "testing",
    Short: "testing app",
}

func init() {
    app.Flags().BoolP("help", "h", false, "helping myself with the command "+app.Name())
}

func main() {
    var name string
    hello := &cobra.Command{
        Use:   "hello",
        Short: "example hello command",
        Run: func(c *cobra.Command, args []string) {
            if name == "" {
                name = "World"
            }
            fmt.Printf("Hello, %s! \n", name)
        },
    }

    hello.Flags().StringVar(&name, "name", "", "name to greet")
    app.AddCommand(hello)

    if err := app.Execute(); err != nil {
        fmt.Println(err.Error())
        os.Exit(1)
    }
}

I would agree if I would be only modifying the help flag for the main function. But if I have a hundred subcommands under testing then it becomes a nightmare, hence me using the PersistentPreRun.

Is there any other way to achieve overriding the whole tree of help flags that's automatically generated?

I've been checking more and it seems you can modify the help _command_ entirely, as well as keeping the features of name interpolation and everything else, but not the Help tag if you have a big list of commands, unless you go one by one and replace them all.

What about modifying InitDefaultHelpFlag() to accept two string parameters, one for the help for string, and the second one for the hardcoded this command?

Anyhow, I ended up doing something easier to avoid having to go one by one. I modified the usage template, and where the .LocalFlags.FlagUsages gets printed and the trimRightSpace function gets called, I wrote a replacer that will find the line containing the help flag, which is usually -h, --help help for abc and then replace help for, returning the new string. I'm using regexs to find the proper line.

If it's not important for you to have a name in the description of your help flag, there is an elegant way to do it with PersistentFlags:

package main

import (
    "fmt"
    "os"

    "github.com/spf13/cobra"
)

var app = &cobra.Command{
    Use:   "testing",
    Short: "testing app",
    Run:   func(c *cobra.Command, args []string) {},
}

func init() {
    app.PersistentFlags().BoolP("help", "h", false, "helping myself with the command")
}

func main() {
    var name string
    hello := &cobra.Command{
        Use:   "hello",
        Short: "example hello command",
        Run: func(c *cobra.Command, args []string) {
            if name == "" {
                name = "World"
            }
            fmt.Printf("Hello, %s! \n", name)
        },
    }

    hello.Flags().StringVar(&name, "name", "", "name to greet")
    app.AddCommand(hello)

    if err := app.Execute(); err != nil {
        fmt.Println(err.Error())
        os.Exit(1)
    }
}

It may work, but there are 2 different help flag descriptions:

  • The standard one that says "help for " + c.Name()
  • A different one that says "help for this command"

I don't know where the second one gets called (I haven't figured it out yet, I know it works on places where there's no command name) but the first one is definitely useful with the name, even more for descriptions or markdown docs.

I don't know where the second one gets called (I haven't figured it out yet, I know it works on places where there's no command name)

You're right, and it's only used there.

InitDefaultHelpFlag defines the help flag:

// InitDefaultHelpFlag adds default help flag to c.
// It is called automatically by executing the c or by calling help and usage.
// If c already has help flag, it will do nothing.
func (c *Command) InitDefaultHelpFlag() {
    c.mergePersistentFlags()
    if c.Flags().Lookup("help") == nil {
        usage := "help for "
        if c.Name() == "" {
            usage += "this command"
        } else {
            usage += c.Name()
        }
        c.Flags().BoolP("help", "h", false, usage)
    }
}

Agreed. So like I mentioned before, what if that InitDefaultHelpFlag allows you to pass your own set of strings for the usage part? That'll make it: a) localizable; and b) easily customizable

The only problem is that this change is not backward compatible

Then keep the function but allow the caller to change the hardcoded strings "help for " and "this command".

I found a solution:

package main

import (
    "fmt"
    "os"

    "github.com/spf13/cobra"
)

var app = &cobra.Command{
    Use:   "testing",
    Short: "testing app",
    Run:   func(c *cobra.Command, args []string) {},
}

func main() {
    var name string
    hello := &cobra.Command{
        Use:   "hello",
        Short: "example hello command",
        Run: func(c *cobra.Command, args []string) {
            if name == "" {
                name = "World"
            }
            fmt.Printf("Hello, %s! \n", name)
        },
    }

    hello.Flags().StringVar(&name, "name", "", "name to greet")
    app.AddCommand(hello)

    walk(app, func(c *cobra.Command) {
        c.Flags().BoolP("help", "h", false, "helping myself with the command "+c.Name())
    })

    if err := app.Execute(); err != nil {
        fmt.Println(err.Error())
        os.Exit(1)
    }
}

// walk calls f for c and all of its children.
func walk(c *cobra.Command, f func(*cobra.Command)) {
    f(c)
    for _, c := range c.Commands() {
        walk(c, f)
    }
}

If you have some questions, don't hesitate to ask.

Can you help me with one thing. I'm trying to search about flags and I'm work with cobra. My object is ./my-binary -ip=127.0.0.1 -mqtt-port=1883 and then I can change the values in my program

my main.go

GNU nano 2.9.3 main.go

package main

import (
"os"

    "github.com/spf13/cobra"

)

func main() {
cmd := &cobra.Command{
Use: "gateway",
Short: "Bem-vindo",
SilenceUsage: true,
}

    cmd.AddCommand(printTimeCmd())
    cmd.AddCommand(printHelloCmd())

    cmd.AddCommand(mainCmd())
    cmd.AddCommand(jardimCmd())

    if err := cmd.Execute(); err != nil {
            os.Exit(1)
    }

}

print_cmd.go

func mainCmd() *cobra.Command {

    return &cobra.Command{
    Use: "sensor",
            RunE: func(cmd *cobra.Command, args []string) error {


     **ip := "tcp://127.0.0.1:1883"**  -> I want to change this with command line with go


    cmd.Println("O IP da gateway e o port s茫o:",ip)


    //Connect to the broker
    opts := MQTT.NewClientOptions()
    opts.AddBroker(ip)
    opts.SetUsername("your-username")
    opts.SetPassword("your-password")


    client := MQTT.NewClient(opts)

    if token := client.Connect(); token.Wait() && token.Error() != nil {
            fmt.Println("Connection error")
            fmt.Println(token.Error())
    }
$ cat main.go 
package main

import (
    "os"

    "github.com/spf13/cobra"
)

const (
    ipFlag = "ip"
)

func main() {
    cmd := &cobra.Command{
        Use:          "gateway",
        Short:        "Bem-vindo",
        SilenceUsage: true,
    }

    cmd.AddCommand(mainCmd())

    if err := cmd.Execute(); err != nil {
        os.Exit(1)
    }
}

func mainCmd() *cobra.Command {
    cmd := &cobra.Command{
        Use: "sensor",
        RunE: func(cmd *cobra.Command, args []string) error {
            ip, err := cmd.Flags().GetString(ipFlag)
            if err != nil {
                return err
            }

            cmd.Println("O IP da gateway e o port s茫o:", ip)
            return nil
        },
    }
    cmd.Flags().String(ipFlag, "tcp://127.0.0.1:1883", "How to connect")
    return cmd
}

$ go run main.go sensor
O IP da gateway e o port s茫o: tcp://127.0.0.1:1883

$ go run main.go sensor --ip=https://bob.com/
O IP da gateway e o port s茫o: https://bob.com/
Was this page helpful?
0 / 5 - 0 ratings

Related issues

garthk picture garthk  路  3Comments

groenborg picture groenborg  路  5Comments

diwakergupta picture diwakergupta  路  5Comments

icholy picture icholy  路  6Comments

rogercoll picture rogercoll  路  5Comments