Cobra is a library for building command line applications in Go. It’s pretty fully featured with sub-commands, automatic completion generators for zsh and bash, and more.
I’m building a helper application to work with my blog publishing pipeline and to keep things modular I’m trying to break it down into smaller utilities.
Cobra is a well established library and feature-rich. It’s got a great helper utility (cobra-cli
) to set up a skeleton you can use to jump in.
One thing that Cobra automatically handles in particular is help output. Veering away from the tradition of docopt
, you build a series of objects, set callbacks and configuration values, and Cobra works backwards from that to generate a nice help output listing flags and all the usual stuff.
Cobra lets you override a few members on the Command
structs to further customize the outputs: Short
and Long
.
What tripped me up as I prototyped the first few commands was the fact that Cobra will simply print whatever strings to provide to Short
and Long
verbatim.
I don’t appreciate this because most of my displays are pretty wide. Paragraphs get smeared across the entire screen and become really challenging to read.
The naïve solution is to just manually terminate my lines at 80 or 120 or some other count. This is “simple” and portable, but extremely tedious.
The other option, as always, is “delegate”. I know that there’s a few different toolkits out there for terminal interaction, but this time I knew what I wanted to use.
charm is a constellation of golang libraries for building terminal applications. My target was lipgloss which handles the bulk of aligning, positioning, and styling terminal output for the rest of Charm’s suite.
lipgloss is nice to work with, it has a pretty simple API that seems to mostly stay out of your way. Cobra, on the other hand, is an extremely opinionated library with lots of (mostly optional) wiring.
The lipgloss documentation has plenty of good examples so for brevity I’m going to jump right into where things got interesting.
The parts of Cobra we care about here are related to the help and usage outputs. These are handled publicly by Command.Set{Help,Usage}Func
, and privately by a handful of unexported functions that take *cobra.Command
and shove that into a template.
Telling Cobra to use our custom Usage and Help functions is pretty straightforward:
|
|
One helpful feature of Cobra is that child commands inherit properties from their parent, including Usage and Help funcs. This means you only have to set this up once on your root command, and your entire application will be able to leverage this.
Below, we have the definitions for each function. As you can see, I’ve managed to cleverly abstract away the hard work and real knowledge by yet another layer; each simply calls out to tmpl()
and pretty()
, both of which will be explained further.
Because tmpl()
is unexported, I had to dig into the Cobra source and copy it out, but that’s coming up. For now, it’s enough to say that it takes a writer, a template string, and a cobra.Command
and executes the template.
The only particularly clever part of this code is leveraging UsageTemplate()
and HelpTemplate()
. My original implementation copied those templates verbatim as well. If all you need to do is wrap the standard output, you can get the built-in template this way.
|
|
Below we’ll find the implementation of pretty()
, a very straightforward function. Take a string and write out the Render()
’d version.
|
|
cobra implements a set of template helper functions, and tmpl(w io.Writer, text string, data interface{}) error
which simply executes a template against a writer.
|
|
Not bad:
You can find the full code here.
I’m not sure if this is even particularly useful yet. There’s edge cases where adding a border causes things to break, and probably more. I’m pretty satisfied with learning more about how cobra is wired up.