The simplest CLI that could possibly work
Summary
Most teams have many little command-line utilities that need to be kept organised. At Screenleap for example I have written tools in Go to manage build artifacts, releases, servers, and local dev environments, to run test clients in various roles, and many other things.
What’s the simplest possible way to put all your ad-hoc tools in one place? We have a single binary called sl
that you run as sl [command] [command options]
.
The sl
main function is basically this:
var commands = map[string]func(){
"thing1": func1,
"thing2": pkgA.Main,
}
func main() {
commands[os.Args[1]]()
}
The commands define their own flags, print errors, everything a normal binary would do. In practice sl
should check os.Args[1] and print what commands are available, so here’s the real code:
func main() {
if len(os.Args) == 1 {
fmt.Println(usage())
os.Exit(1)
}
cmd, ok := commands[os.Args[1]]
if !ok {
fmt.Println(usage())
os.Exit(1)
}
cmd()
}
func usage() string {
s := "Usage: sl [cmd] [options]\nCommands:\n"
for k := range commands {
s += " - " + k + "\n"
}
return s
}
Sure you could use one of the Go command line frameworks out there, but then you’re imposing a dependency on your colleagues. We’ve been using the above map-of-functions approach since I joined five years ago because I couldn’t think of a simpler way to start, and so far we’re happy with it.
Advantages:
- Single binary. Everyone knows where to look for a tool, there’s only one file to `scp` around, and in Go it saves a lot of disk space.
- Our servers are in Go, and these command line tools use the same libraries as the servers. It makes experimenting with a scenario quite fast, you can script with the server domain objects.
- No requirements on the individual commands, a command is just a function. The commands print their own usage documentation.
- No dependencies.
Happy tooling!