Choosing a Go CLI Library
Share: 

Choosing a Go CLI Library

7 March 2021

Over the years I’ve used many golang CLI modules. Some have just parsed flags, some have been whole “frameworks”; halfway to a gokit or go micro. For a long time I was a big fan of the classic cobra/viper combo, and I think they still have merit.

However lately I’ve written a few little utilities that have needed to take args from flags, but don’t want a whole framework trying to wrestle control from them. In particular, I didn’t want Inversion of Control problems.

So I set about choosing a new favourite CLI library. My criteria were:

  • Testable (can I at least pass in a dummy os.Args?)
  • Type safe (so far as go is)
  • Auto-generation of usage
  • Auto-generation of bash completion
  • Consume flags and their args, leaving positional args
  • Ideally, some support for verbs / actions / sub-commands

Results

Well, I didn’t even test most of them. The first one I tried was go-flags and it rocks. I was satisficed, so I stopped.

Library Testable Type Safe Usage Completion Positionals Verbs
pkg/flag ? ? ? ? ? ?
cobra ? ? ? ? ? ?
urfave/cli/v2 ? ? ? ? ? ?
uber? ? ? ? ? ? ?
kingpin ? ? ? ? ? ?
mow.cli ? ? ? ? ? ?
argparse ? ? ? ? ? ?
mitchelh/cli ? ? ? ? ? ?
go-flags yes yes yes yes yes yes

Example

// Declare a variable to take the parsed flags.
// You can make a type, or just use an anonymous struct on the fly like this.
var opts struct {
      Type       bool `short:"t" long:"type" description:"Print the type"`
      TypeSymbol bool `short:"T" long:"type-symbol" description:"..."`
      Unit       bool `short:"u" long:"unit" description:"Print the unit"`
      Name       bool `short:"n" long:"name" description:"Print the name"`
}

// Parse the command line.
// flags.Parse() implicity uses os.Args, and drops the 0th element.
args, err := flags.Parse(&opts)
if err != nil {
      panic(err)
}

// args now contains anything that didn't match a specification in opts.
fmt.Println("Positional args: ", args)