Cmder is a ~100 LOC pattern that has been used as the foundation of all the Go CLIs I've written (including the Sourcegraph CLI.)
I've often just suggested others simply copy the pattern as it is so lightweight. Now you can import it as a Go package, which helps to document the pattern you're using, and avoids temptation to make it more complex than needed.
- Mimics what the official
go
tool does internally. - Merely builds upon the
flag
package to support subcommands. - Supports subcommand flags, subcommands of subcommands, etc.
An example/fictional HTTP tool called kurl
is provided in the example/kurl
directory.
The idea is simple, import the package:
import "github.com/hexops/cmder"
Declare a list of your subcommands:
// commands contains all registered subcommands.
var commands cmder.Commander
Append a few subcommands like so (usually one init
function per subcommand):
flagSet := flag.NewFlagSet("foo", flag.ExitOnError)
commands = append(commands, &cmder.Command{
FlagSet: flagSet,
Handler: func(args []string) error {
_ = flagSet.Parse(args)
return nil
},
})
In your main
function, call:
commands.Run(flag.CommandLine, commandName, usageText, os.Args[1:])
- Consult
go help
for inspiration on how to write yourusageText
. - Register subcommand flags by using e.g.
flagSet.Bool
(as you would've if using the Goflag
package otherwise.) - Need subcommands in your subcommands? Declare another set of
commands
and simply call theRun
method inside your subcommandHandler
.
Consult the API documentation for more information.
We're open to considering improvements, but since this pattern has been in use in various CLIs over the past 3-4 years, we likely won't make any major changes to the API or introduce new features. The aim is to keep it minimal and simple.
Some popular alternatives which aim to be simple:
- peterbourgon/ff/ffcli (quite good; also has config file parsing)
- google/subcommands (not quite as minimal as cmder)
Some popular alternatives which provide as many features and knobs as you could want include:
Fixed an issue where subcommands incorrectly had their flags parsed twice. Handlers should always call flagSet.Parse(args)
on the arguments passed to them, as demonstrated in example/kurl/get.go
.
Initial release.