5

Using cobra, if my app is invoked without a specific action (but arguments), I'd like to run a default command:

// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
    Use:   "mbmd",
    Short: "ModBus Measurement Daemon",
    Long:  "Easily read and distribute data from ModBus meters and grid inverters",

    Run: func(cmd *cobra.Command, args []string) {
        run(cmd, args)
    },
}

However, since the root command doesn't have all arguments the child command has this fails as it's apparently now aware of the child command's arguments:

❯ go run main.go -d sma:126@localhost:5061 --api 127.1:8081 -v
Error: unknown shorthand flag: 'd' in -d

as opposed to:

❯ go run main.go run -d sma:126@localhost:5061 --api 127.1:8081 -v
2019/07/29 20:58:10 mbmd unknown version (unknown commit)

How can I programmatically instantiate/invoke a child command?

andig
  • 13,378
  • 13
  • 61
  • 98
  • By calling `run()`. Every command in Cobra just calls a function. You can call the same function from multiple cobra commands. – Jonathan Hall Jul 29 '19 at 18:53
  • As I've done? That only honors the root command arguments, but not the run command's arguments :/ – andig Jul 29 '19 at 18:54
  • You have now (Oct. 2021) have two possible workarounds. See [my answer below](https://stackoverflow.com/a/66795805/6309). – VonC Oct 22 '21 at 15:59

2 Answers2

3

Here is another solution:

    cmd, _, err := rootCmd.Find(os.Args[1:])
    // default cmd if no cmd is given
    if err == nil && cmd.Use == rootCmd.Use && cmd.Flags().Parse(os.Args[1:]) != pflag.ErrHelp {
        args := append([]string{defaultCmd.Use}, os.Args[1:]...)
        rootCmd.SetArgs(args)
    }

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

Replace defaultCmd with one you want to be default

This part cmd.Flags().Parse(os.Args[1:]) != pflag.ErrHelp keeps help command working for root command if no arguments was set

Brun
  • 388
  • 3
  • 11
2

March 2021: You might consider a workaround as the one presented in spf13/cobra issue 823

func subCommands() (commandNames []string) {
    for _, command := range cmd.Commands() {
        commandNames = append(commandNames, append(command.Aliases, command.Name())...)
    }
    return
}

func setDefaultCommandIfNonePresent() {
    if len(os.Args) > 1 { 
        potentialCommand := os.Args[1]
        for _, command := range subCommands() {
            if command == potentialCommand {
                return
            }
        }
        os.Args = append([]string{os.Args[0], "<default subcommand>"}, os.Args[1:]...)
    }

}

func main() {
    setDefaultCommandIfNonePresent()
    if err := cmd.Execute(); err != nil {
        zap.S().Error(err)
        os.Exit(1)
    }
}

The difference here is that it checks if len(os.Args) > 1 before changing the default subcommand.

This means that, if ran without any arguments, it will print the default help command (with all of the subcommands).
Otherwise, if supplied any arguments, it will use the subcommand.

So, it will display the main 'help' without arguments, and the subcommand's help if supplied '-h'/'--help'.


Or (Oct. 2021), from the author of PR 823:

Latest solve for this is the following:

main.go

func main() {
  // Define the default sub command 'defCmd' here. If user doesn't submit
  // using a default command, we'll use what is here.
  defCmd:="mydefaultcmd"
  cmd.Execute(defCmd)
}

root.go

func Execute(defCmd string) {
  var cmdFound bool
  cmd :=rootCmd.Commands()

  for _,a:=range cmd{
    for _,b:=range os.Args[1:] {
      if a.Name()==b {
       cmdFound=true
        break
      }
    }
  }
  if !cmdFound {
    args:=append([]string{defCmd}, os.Args[1:]...)
    rootCmd.SetArgs(args)
  }
  if err := rootCmd.Execute(); err != nil {
    fmt.Println(err)
    os.Exit(1)
  }
}
andig
  • 13,378
  • 13
  • 61
  • 98
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250